为什么多次添加0.1会保持无损?

icz*_*cza 148 java floating-point precision double

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

已经说过,添加0.1 三次不会完全给出0.3以下代码打印是可以理解的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
Run Code Online (Sandbox Code Playgroud)

但是,如何增加0.1 五次才会给出确切的答案0.5呢?以下代码打印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?
Run Code Online (Sandbox Code Playgroud)

如果0.1不能准确表示,那么如何将它添加5次0.5可以精确地表示哪个?

Pet*_*rey 153

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

例如0.1不完全0.1,即new BigDecimal("0.1") < new BigDecimal(0.1),但0.5也正是1.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);
}
Run Code Online (Sandbox Code Playgroud)

版画

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
Run Code Online (Sandbox Code Playgroud)

注意:0.3稍微关闭,但是当你到达0.4位时必须向下移一个以适应53位限制并且错误被丢弃.再次,误差在用于爬行背部0.60.70.81.0该错误将被丢弃.

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

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

这样的一个例子就是为什么在Java 6中为什么Math.round(0.49999999999999994)返回1在这种情况下,计算中的一点损失会导致答案产生很大的差异.

  • @Zhuinden CPU遵循IEEE-754标准.Java允许您访问底层CPU指令,而不涉及.http://en.wikipedia.org/wiki/IEEE_floating_point (16认同)
  • @PeterLawrey:不一定是CPU.在CPU中没有浮点的机器上(并且没有使用单独的FPU),IEEE算法将由软件执行.如果主机CPU有浮点但不符合IEEE要求,我认为该CPU的Java实现也有义务使用软浮点数... (10认同)
  • @eugene的关键问题是浮点数可以表示的有限值.这种限制可能导致信息丢失,并且随着数量的增加而失去错误.它使用舍入,但在这种情况下,向下舍入,这将是一个略大于0.1的数字稍微过大,变成正确的值.恰好0.5 (2认同)

Pas*_*uoq 47

禁止溢出,在浮点,x + x + x正好是真实3*的正确舍入(即最接近)浮点数x,x + x + x + x正好是4*x,并且x + x + x + x + x再次是5*的正确舍入的浮点近似x.

第一个结果x + x + x来自于x + x确切的事实.x + x + x因此只是一次舍入的结果.

第二个结果更加困难,这里将讨论它的一个演示(并且Stephen Canon通过最后3位数字的案例分析暗示另一个证明x).总而言之,3*与2*x位于同一个binade中,x或者与4*位于同一个binade中x,并且在每种情况下都可以推断出第三次加法时的误差会消除第二次加法时的误差(正如我们已经说过的那样,第一次添加是准确的.

第三个结果," x + x + x + x + x正确舍入",从第二个结果推导出第二个结果,第一个结果来自于第二个结果的正确性x + x.


第二个结果解释了为什么0.1 + 0.1 + 0.1 + 0.1正好是浮点数0.4:当转换为浮点时,有理数1/10和4/10以相同的方式近似,具有相同的相对误差.这些浮点数之间的比率恰好为4.第一和第三结果显示,0.1 + 0.1 + 0.10.1 + 0.1 + 0.1 + 0.1 + 0.1可以预期比可能由幼稚错误分析推断的错误少,但是,在他们自己,他们只与结果分别3 * 0.15 * 0.1,可以预计将接近但不一定相同0.30.5.

如果你0.1在第四次添加之后继续添加,你将最终观察到" 0.1使自身增加n次"的舍入误差n * 0.1与n/10相差甚远.如果你将"0.1加到它自己n"的值作为n的函数绘制,你会观察到由于binades的恒定斜率线(一旦第n次加法的结果注定落入特定的binade,可以预期添加的性质类似于在相同的binade中产生结果的先前添加物.在同一个binade中,错误会增大或缩小.如果您要查看从binade到binade的斜率序列,您会认识到0.1二进制的重复数字一段时间.之后,吸收将开始发生,曲线将趋于平缓.

  • +1这应该是接受的答案.它实际上提供了解释/证明正在发生的事情,而不仅仅是模糊的一般性. (4认同)
  • @Alboz我说`x + x + x`正好是正确的**舍入的**浮点数到真正的3*`x`."正确舍入"在这种情况下意味着"最接近". (2认同)