如果您编写以下代码会怎样:
int x = 0;
for(int i = 0; i< 100; i++)
{
x = x + 3.5;
NSLog(@"%d",x);
}
你不会对它没有打印出来感到惊讶,,,,对3.5
吧?你会说“int 不准确”吗?当然不是。7.0
10.5
在您的示例中发生了完全相同的事情。就像3.5
不可表示为整数,0.01
不可表示为双精度。你得到的实际价值是:
0.01000000000000000020816681711721685132943093776702880859375
现在,您不仅会累积从舍入到加倍的初始表示误差0.1
,而且还会得到舍入误差,因为并非所有中间和都是可表示的。int
示例中没有出现第二个错误源。计算的实际中间总和是:
0.01000000000000000020816681711721685132943093776702880859375
0.0200000000000000004163336342344337026588618755340576171875
0.0299999999999999988897769753748434595763683319091796875
0.040000000000000000832667268468867405317723751068115234375
0.05000000000000000277555756156289135105907917022705078125
0.060000000000000004718447854656915296800434589385986328125
...
0.990000000000000657252030578092671930789947509765625
1.0000000000000006661338147750939242541790008544921875
当您通过格式说明符将这些四舍五入到小数点后六位时%f
,您将获得第三个四舍五入来源,但错误都足够小,以至于您“预期”的结果会被打印出来。
现在让我们看看当你使用float
代替时会发生什么double
;由于 C 算术操作数提升规则,所有加法都在 中进行double
,但结果float
在每次加法后四舍五入 - 又一次四舍五入。中间值的顺序如下:
0.00999999977648258209228515625
0.0199999995529651641845703125
0.02999999932944774627685546875
0.039999999105930328369140625
0.0500000007450580596923828125
0.060000002384185791015625
...
0.809999525547027587890625
0.819999516010284423828125
0.829999506473541259765625
到目前为止,错误足够小,当四舍五入到六位十进制数字时,它们仍然会产生“预期”值。但是,计算的下一个值是
0.839999496936798095703125
因为这只是小于四舍五入到六位十进制数字的确切中间情况:
0.8399995
它向下取整,打印的数字是:
0.8399999
现在,你能做些什么呢?错误最终变得大到足以在使用六位十进制数字打印时出现的原因是您在每次连续添加时都会累积错误。如果你能避免这种累积,误差将保持足够小,不会造成这种麻烦。有几种简单的方法可以避免这种累积错误;也许最简单的是:
for (int i=0; i<100; i++) {
float x = (i+1)/100.f;
NSLog(@"%d",x);
}
This works because both i + 1
and 100.f
are exactly represented in float
. There is thus only a single rounding, which occurs in the division, so the float
result is as close to your desired number as possible; there is no way you can come any closer.