浮点结果不准确

时间:2013-01-30 15:53:20

标签: ios objective-c floating-point

  

可能重复:
  Trouble with floats in Objective-C

这听起来像是一个基本问题,但我正在尝试2天才弄清楚。 我搜索了一个解决方案,我发现的唯一解释是float只使用4个字节的内存。 (这很有帮助......) 我有以下循环:

double x = 0.0;
        for(int i = 0; i< 100; i++)
        {
            x = x + 0.01;
            NSLog(@"%f",x);
        }

并打印:

0.010000
0.020000
0.030000
.
.
1.000000

但当我将double更改为float时:

0.010000
0.020000
0.030000
.
.
0.820000
0.830000
0.839999
0.849999
.
.
0.999999

如您所见,计算机无法计算0.840000作为浮点数__- 问题是我必须使用float,因为我使用的UIProgressView可以使用0.0到1.0之间的浮点数。

如果我不能使用双倍,我该怎么办? 感谢。

2 个答案:

答案 0 :(得分:3)

使用x = (i + 1) * 0.01;代替x = x + 0.01;。它仍然不准确,因为float根本无法准确表示0.84,但至少错误不会累积。

答案 1 :(得分:3)

如果您编写以下代码该怎么办:

int x = 0;
for(int i = 0; i< 100; i++)
{
    x = x + 3.5;
    NSLog(@"%d",x);
}

如果不打印3.57.010.5,你不会感到惊讶,对吗?你会说“int是不准确的”吗?当然不是。

在你的例子中正好发生了同样的事情。就像3.5不能表示为整数一样,0.01不能表示为double。你得到的实际价值是:

0.01000000000000000020816681711721685132943093776702880859375

现在,您不仅累积了从舍入0.1到double的初始表示错误,而且还得到舍入错误,因为并非所有中间总和是可以代表的。 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);
}

这是有效的,因为i + 1100.f都在float中得到了完整的体现。因此,只有一个舍入,发生在除法中,因此float结果尽可能接近您想要的数字;你无法靠近。