关于java:为什么多次添加0.1会无损?

Why does adding 0.1 multiple times remain lossless?

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

已经说过,将0.1加3次并不能得到确切的0.3,所以下面的代码打印false,这是可以理解的:

1
2
3
4
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

1
2
3
4
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


舍入误差不是随机的,在实现舍入误差的过程中,它试图将误差最小化。这意味着有时错误不可见,或者没有错误。

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

这个程序向您显示所涉及的真实值。

1
2
3
4
5
6
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);
}

印刷品

1
2
3
4
5
6
7
8
9
10
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,错误被丢弃了。

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

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

这就是为什么在Java 6中,为什么数学计算(0.4999999999999999)返回1,在这种情况下,计算结果中的一点丢失与答案有很大的不同。


除溢出外,在浮点运算中,x + x + x正好是正确四舍五入(即最近的)浮点数到实数3*xx + x + x + x正好是4*xx + x + x + x + x又是5*x的正确四舍五入浮点近似。

对于x + x + x来说,第一个结果是由于x + x是精确的。因此,x + x + x仅是一次舍入的结果。

第二个结果比较困难,本文讨论了其中一个证明(斯蒂芬·卡农引用了对x最后3位数字的另一个实例分析)。综上所述,3*x与2*x在同一个二进制代码中,或者与4*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的函数绘制出来,你会观察到二进制数的等斜率线(当第n次相加的结果注定要落在一个特定的二进制数中时,加法的性质可以预期与以前的加法相似,后者会产生相同的b。内酰胺)。在同一个二进制代码中,错误将增大或缩小。如果你观察从二进制码到二进制码的斜坡序列,你会在一段时间内识别出0.1在二进制码中的重复数字。之后,吸收开始发生,曲线变平。


浮点系统有各种各样的魔力,包括有一些额外的位精度为四舍五入。因此,由于0.1的不精确表示而产生的非常小的误差最终被四舍五入为0.5。

把浮点看成是表示数字的一种伟大但不精确的方法。并非所有可能的数字都能轻易地在计算机中表示出来。像π这样的无理数。或者像sqrt(2)。(符号数学系统可以代表它们,但我确实说过"容易"。)

浮点值可能非常接近,但不精确。它可能是如此的接近以至于你可以导航到冥王星并且以毫米为单位离开。但在数学意义上仍然不准确。

当需要精确而非近似时,不要使用浮点。例如,会计应用程序希望准确跟踪一个帐户中的某个硬币数。整数是很好的,因为它们是精确的。使用整数时需要注意的主要问题是溢出。

使用bigdecimal表示货币效果很好,因为基础表示是一个整数,尽管它很大。

由于认识到浮点数是不精确的,它们仍然有许多用途。导航坐标系或图形系统中的坐标。天文值。科学价值观。(你可能不知道棒球的确切质量在一个电子的质量范围内,所以不准确并不重要。)

对于计数应用程序(包括记帐),请使用整数。为了计算通过一个门的人数,使用int或long。