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

时间:2014-09-30 11:59:49

标签: java floating-point double precision

我知道0.1十进制数无法用有限二进制数(explanation)精确表示,因此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

但是,如何添加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?

如果无法准确表示0.1,那么将其添加5次会如何精确地表示0.5

3 个答案:

答案 0 :(得分:150)

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

例如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);
}

打印

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,错误将被丢弃。

  

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

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

这就是为什么在Java 6中Why does Math.round(0.49999999999999994) return 1在这种情况下,计算中丢失一点会导致答案产生很大的差异。

答案 1 :(得分:46)

禁止溢出,在浮点数中,x + x + x正好是真实3 * x的正确舍入(即最近)浮点数,x + x + x + x正好是4 * { {1}},x再次是5 * x + x + x + x + x的正确舍入浮点近似值。

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

第二个结果更难,其中一个示例被讨论here(而Stephen Canon通过对x + x + x的最后3位数字的案例分析提示另一个证据)。总而言之,3 * x与2 * x位于同一binade或与4 * x位于同一个binade中,并且在每种情况下都是可以推断出第三次加法时的错误会消除第二次加法时的错误(第一次加法是准确的,正如我们已经说过的那样)。

第三个结果,“x被正确舍入”,从第二个结果推导出与第一个结果相同的方式,第一个结果来自x + x + x + x + x的正确性。


第二个结果解释了为什么x + x正好是浮点数0.1 + 0.1 + 0.1 + 0.1:有理数1/10和4/10以相同的方式近似,具有相同的相对误差,转换时到浮点。这些浮点数之间的比率恰好为4。第一个和第三个结果表明,0.40.1 + 0.1 + 0.1可能比天真错误分析所推断的错误更少,但是,它们本身只将结果与{{1}相关联。和0.1 + 0.1 + 0.1 + 0.1 + 0.1,可以预期接近但不一定与3 * 0.15 * 0.1相同。

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

答案 2 :(得分:-1)

浮点系统具有各种魔力,包括为了舍入而具有一些额外的精度。因此,由于0.1的不精确表示而导致的非常小的误差最终变为0.5。

将浮点视为表示数​​字的一种很好但不可行的方式。并非所有可能的数字都可以在计算机中轻松表示。像PI这样的非理性数字。或者像SQRT(2)。 (符号数学系统可以代表它们,但我确实说过&#34;很容易&#34;。)

浮点值可能非常接近,但不准确。它可能非常接近你可以导航到冥王星并以毫米为单位。但在数学意义上仍然不准确。

当您需要精确而不是近似时,请不要使用浮点数。例如,会计应用程序希望精确跟踪帐户中的一定数量的便士。整数对此有好处,因为它们是准确的。使用整数需要注意的主要问题是溢出。

使用BigDecimal作为货币很有效,因为底层表示是一个整数,虽然很大。

认识到浮点数不精确,它们仍然有很多用途。协调图形系统中的导航或坐标系统。天文价值观。科学价值观。 (无论如何,你可能无法知道棒球在电子质量范围内的精确质量,所以不精确并不重要。)

对于计数应用程序(包括会计),请使用整数。要计算通过门的人数,请使用int或long。