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

2022-08-31 07:31:44

我知道十进制数不能用有限的二进制数(解释)精确表示,因此会失去一些精度并且不会完全。另一方面,可以完全表示,因为它是 。0.1double n = 0.10.10.50.5 = 1/2 = 0.1b

话虽如此,可以理解的是,添加三次不会给出确切的结果,因此以下代码打印:0.10.3false

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.10.5true

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

如果不能被精确地表示,那么将它加5次,究竟能给出哪些可以精确地表示呢?0.10.5


答案 1

舍入误差不是随机的,其实现方式会尝试将误差最小化。这意味着有时错误不可见,或者没有错误。

例如不完全是 但正是0.10.1new BigDecimal("0.1") < new BigDecimal(0.1)0.51.0/2

此程序显示所涉及的真实值。

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

注意:这稍微偏离了一点,但是当您到达位时,必须向下移动一个位以适应53位限制,并且错误将被丢弃。同样,错误会爬回 for 和 for,但 for 会丢弃该错误。0.30.40.60.70.81.0

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

出现错误的原因是由于精度有限。即 53 位。这意味着,随着数字越来越大,它使用更多的位,位必须从末端掉下来。这会导致四舍五入,在这种情况下,这对您有利。
当获得较小的数字时,您可能会得到相反的效果,例如: =>,您会看到比以前更多的错误。0.1-0.09991.0000000000000286E-4

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


答案 2

在浮点中,排除溢出正好是与实数 3* 正确舍入(即最接近)的浮点数,正好是 4*,并且再次是 5* 的正确舍入浮点近似值。x + x + xxx + x + x + xxx + x + x + x + xx

第一个结果 for 从精确的事实派生出来。 因此,只有一个舍入的结果。x + x + xx + xx + x + x

第二个结果更困难,这里讨论了它的一个演示(Stephen Canon通过对最后3位数字的案例分析暗示了另一个证明)。总而言之,要么 3* 与 2* 位于同一二元区中,要么与 4* 位于同一二元组中,并且在每种情况下,都可以推断出第三次加法时的错误取消了第二次加法时的错误(正如我们已经说过的,第一次加法是准确的)。xxxx

第三个结果“正确舍入”从第二个结果派生出来,就像第一个结果从 的精确性推导出来一样。x + x + x + x + xx + x


第二个结果解释了为什么是浮点数:当转换为浮点数时,有理数1/10和4/10以相同的方式近似,具有相同的相对误差。这些浮点数之间的比率正好为 4。第一个和第三个结果表明,并且可以预期其误差小于由朴素误差分析推断的误差,但是,就其本身而言,它们仅将结果分别与 和 相关联,可以预期结果与 和 接近但不一定相同。0.1 + 0.1 + 0.1 + 0.10.40.1 + 0.1 + 0.10.1 + 0.1 + 0.1 + 0.1 + 0.13 * 0.15 * 0.10.30.5

如果你在第四次加法之后继续加法,你最终会观察到舍入误差,这些误差使“加到自身n次”偏离,并且与n/10发散得更多。如果要将“0.1 加到自身 n 次”的值绘制为 n 的函数,则通过二元数观察到斜率恒定的线(一旦第 n 次加法的结果注定要落入特定的二元组,就可以预期加法的属性与先前的加法相似,从而产生相同的二元数)。在同一个二进制文件中,错误将增加或缩小。如果您要查看从binade到binade的斜率序列,您会在一段时间内识别出二进制的重复数字。之后,吸收将开始发生,曲线将变平。0.10.1n * 0.10.1