更新的准确性和完整性
您遇到的是浮点数精度的(相当常见的)问题。在许多编译器上,浮点数只有 32 位长,它们使用一定数量的这些位 (23) 作为它们的“有效位”(有时称为“尾数”)、1 个符号位和 8 位作为“指数”(在像 1.23E45 这样的数字中,“1.23”是有效数,“45”是指数。在二进制中,您实际上只有相对较少 (24) 个可用的 1 和 0,因此您在大约数字 6 或 7 的十进制表示法)。
为了说明这种精度损失,我写了几行代码:
#include <stdio.h>
int main(){
float fpennies;
long lpennies, ii;
lpennies = 805306360;
for(ii = 0; ii< 100; ii++) {
fpennies = lpennies + ii;
printf("%ld pennies converted to float: %.0f fpennies\n",ii+ lpennies, fpennies);
}
}
这产生了许多类型的行
805306360 pennies converted to float: 805306368 fpennies
805306361 pennies converted to float: 805306368 fpennies
805306362 pennies converted to float: 805306368 fpennies
...
805306400 pennies converted to float: 805306368 fpennies
805306401 pennies converted to float: 805306432 fpennies
如您所见,就在 附近805306400
,将 增加long
1float
会使数字的表示增加64
!最好通过仔细查看浮点数的二进制表示来解释这一点。
首先,这里是一个 32 位浮点数的组织(来自http://upload.wikimedia.org/wikipedia/commons/d/d2/Float_example.svg):
我们可以通过一些显式转换获得数字的十六进制表示:
printf("%.0f %08x", fpennies, *(unsigned int*)(&fpennies));
对于我们之前看到的跨越跳跃的两个值,这导致
805306368 4e400000
805306432 4e400001
如您所见,有效数字的“最低有效位”增加了1
,但指数意味着乘数为 64。为什么是 64?好吧,让我们扩展前几位:
0x4e40 = 0100 1110 0100 0000 in binary
由于最高位是符号位(0 = 正),接下来的八位是指数,这使得指数
1001 1100 = 0x9c = 156
现在,从浮点中的位到其值的规则(参见http://en.wikipedia.org/wiki/Single-precision_floating-point_format)是
value = (-1)^(sign bit) * (1 + sum(i=1 to 23, bit(23-i)*2^(-i))) * 2^(exponent - 127)
在这种情况下,1
最低有效位(位 0)的变化增加了2^(-23) * 2^( 156 - 127 ) = 2^6 = 64
因此,对于这种数量级的数字,可以表示的最小步长是64
,正如您在输出中看到的那样。
如果你想解决这个问题,你可以做 Vaughn 的回答中建议的事情 - 使用表示便士的长整数,并使用整数数学(除法,模数)来获得“整美元,整美分”的金额。
long int dollars, cents, pennies;
...
dollars = pennies / 100;
cents = pennies % 100;
通过这种方式,您可以在不损失精度的情况下代表一些相当大的金额。
在实践中,当你写
float pennies = 805306365;
printf("you have %f pennies\n", pennies);
你得到
You have 805306368 pennies
如果你使用一种double
类型,你会有更好的运气(在这种情况下)。