为什么多次加0.1仍然无损?

2022-01-09 00:00:00 floating-point precision double java

我知道 0.1 十进制数不能用有限的二进制数精确表示(explanation),所以 double n = 0.1 会损失一些精度,不会完全是 0.1.另一方面,0.5 可以精确表示,因为它是 0.5 = 1/2 = 0.1b.

I know the 0.1 decimal number cannot be represented exactly with a finite binary number (explanation), so double n = 0.1 will lose some precision and will not be exactly 0.1. On the other hand 0.5 can be represented exactly because it is 0.5 = 1/2 = 0.1b.

话虽如此,添加 0.1 三次 不会给出完全正确的 0.3 是可以理解的,所以下面的代码打印出 false:

Having said that it is understandable that adding 0.1 three times will not give exactly 0.3 so the following code prints false:

double sum = 0, d = 0.1;
for (int i = 0; i < 3; i++)
    sum += d;
System.out.println(sum == 0.3); // Prints false, OK

但是,如何添加 0.1 五次 会恰好得到 0.5?以下代码打印 true:

But then how is it that adding 0.1 five times will give exactly 0.5? The following code prints true:

double sum = 0, d = 0.1;
for (int i = 0; i < 5; i++)
    sum += d;
System.out.println(sum == 0.5); // Prints true, WHY?

如果0.1不能准确表示,怎么加5次就正好0.5可以准确表示?

If 0.1 cannot be represented exactly, how is it that adding it 5 times gives exactly 0.5 which can be represented precisely?

推荐答案

舍入误差不是随机的,它的实现方式试图最小化误差.这意味着有时错误是不可见的,或者没有错误.

The rounding error is not random and the way it is implemented it attempts to minimise the error. This means that sometimes the error is not visible, or there is not error.

例如 0.1 不完全是 0.1new BigDecimal("0.1") <new BigDecimal(0.1)0.5 正是 1.0/2

For example 0.1 is not exactly 0.1 i.e. new BigDecimal("0.1") < new BigDecimal(0.1) but 0.5 is exactly 1.0/2

这个程序向您展示了所涉及的真正价值.

This program shows you the true values involved.

BigDecimal _0_1 = new BigDecimal(0.1);
BigDecimal x = _0_1;
for(int i = 1; i <= 10; i ++) {
    System.out.println(i+" x 0.1 is "+x+", as double "+x.doubleValue());
    x = x.add(_0_1);
}

打印

0.1000000000000000055511151231257827021181583404541015625, as double 0.1
0.2000000000000000111022302462515654042363166809082031250, as double 0.2
0.3000000000000000166533453693773481063544750213623046875, as double 0.30000000000000004
0.4000000000000000222044604925031308084726333618164062500, as double 0.4
0.5000000000000000277555756156289135105907917022705078125, as double 0.5
0.6000000000000000333066907387546962127089500427246093750, as double 0.6000000000000001
0.7000000000000000388578058618804789148271083831787109375, as double 0.7000000000000001
0.8000000000000000444089209850062616169452667236328125000, as double 0.8
0.9000000000000000499600361081320443190634250640869140625, as double 0.9
1.0000000000000000555111512312578270211815834045410156250, as double 1.0

注意:0.3 稍微偏离了一点,但是当您到达 0.4 时,位必须向下移动一位以适应 53 位限制,并且错误是丢弃.同样,0.60.7 的错误再次出现,但对于 0.81.0,错误被丢弃.

Note: that 0.3 is slightly off, but when you get to 0.4 the bits have to shift down one to fit into the 53-bit limit and the error is discarded. Again, an error creeps back in for 0.6 and 0.7 but for 0.8 to 1.0 the error is discarded.

添加 5 次应该会累积错误,而不是取消它.

Adding it 5 times should cumulate the error, not cancel it.

出现错误的原因是精度有限.即53位.这意味着随着数字变大使用更多位,必须从末尾丢弃位.这会导致舍入,在这种情况下对您有利.
当获得较小的数字时,您可以获得相反的效果,例如0.1-0.0999 => 1.0000000000000286E-4你会看到比以前更多的错误.

The reason there is an error is due to limited precision. i.e 53-bits. This means that as the number uses more bits as it get larger, bits have to be dropped off the end. This causes rounding which in this case is in your favour.
You can get the opposite effect when getting a smaller number e.g. 0.1-0.0999 => 1.0000000000000286E-4 and you see more error than before.

这方面的一个例子是为什么在 Java 6 为什么 Math.round(0.49999999999999994) return 1 在这种情况下,计算中丢失一个位会导致答案有很大差异.

An example of this is why in Java 6 Why does Math.round(0.49999999999999994) return 1 In this case the loss of a bit in calculation results in a big difference to the answer.

相关文章