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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26120311/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 09:19:36  来源:igfitidea点击:

Why does adding 0.1 multiple times remain lossless?

javafloating-pointdoubleprecision

提问by icza

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

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

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

话虽如此,可以理解的是,添加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

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

但是,加上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?

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

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

回答by Peter Lawrey

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.

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

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

例如0.1,不完全是0.1ienew BigDecimal("0.1") < new BigDecimal(0.1)而是0.5完全是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);
}

prints

印刷

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

Note: that 0.3is slightly off, but when you get to 0.4the 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.6and 0.7but for 0.8to 1.0the error is discarded.

注意:这0.3有点偏离,但是当您到达0.4位时,必须向下移一位以适应 53 位限制,并且错误将被丢弃。再次,一个错误蠕变回来为0.60.7,但对于0.81.0错误被丢弃。

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

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

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-4and you see more error than before.

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

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

这方面的一个例子是为什么在 Java 6中 Math.round(0.499999999999999994) 返回 1在这种情况下,计算中丢失一点会导致与答案的巨大差异。

回答by Pascal Cuoq

Barring overflow, in floating-point, x + x + xis exactly the correctly rounded (i.e. nearest) floating-point number to the real 3*x, x + x + x + xis exactly 4*x, and x + x + x + x + xis again the correctly rounded floating-point approximation for 5*x.

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

The first result, for x + x + x, derives from the fact that x + xis exact. x + x + xis thus the result of only one rounding.

对于 ,第一个结果x + x + x来自于x + x精确的事实。x + x + x因此是只四舍五入的结果。

The second result is more difficult, one demonstration of it is discussed here(and Stephen Canon alludes to another proof by case analysis on the last 3 digits of x). To summarize, either 3*xis in the same binadeas 2*xor it is in the same binade as 4*x, and in each case it is possible to deduce that the error on the third addition cancels the error on the second addition (the first addition being exact, as we already said).

第二个结果更难,这里讨论了它的一个证明(斯蒂芬·卡农在 的最后 3 位数上通过案例分析暗示了另一个证明x)。总之,无论是3 *x是在同一binade2 *x或它是在相同的binade为4 * x,并在每种情况下,可以推断,在第三加法错误取消在第二加法错误(正如我们已经说过的,第一个添加是准确的)。

The third result, “x + x + x + x + xis correctly rounded”, derives from the second in the same way that the first derives from the exactness of x + x.

第三个结果“x + x + x + x + x被正确四舍五入”从第二个结果中得出,与第一个从 的精确性中得出的方式相同x + x



The second result explains why 0.1 + 0.1 + 0.1 + 0.1is exactly the floating-point number 0.4: the rational numbers 1/10 and 4/10 get approximated the same way, with the same relative error, when converted to floating-point. These floating-point numbers have a ratio of exactly 4 between them. The first and third result show that 0.1 + 0.1 + 0.1and 0.1 + 0.1 + 0.1 + 0.1 + 0.1can be expected to have less error than might be inferred by naive error analysis, but, in themselves, they only relate the results to respectively 3 * 0.1and 5 * 0.1, which can be expected to be close but not necessarily identical to 0.3and 0.5.

第二个结果解释了为什么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.1与 和相关联5 * 0.1,可以预期与 和 接近但不一定相同0.30.5

If you keep adding 0.1after the fourth addition, you will finally observe rounding errors that make “0.1added to itself n times” diverge from n * 0.1, and diverge even more from n/10. If you were to plot the values of “0.1 added to itself n times” as a function of n, you would observe lines of constant slope by binades (as soon as the result of the nth addition is destined to fall into a particular binade, the properties of the addition can be expected to be similar to previous additions that produced a result in the same binade). Within a same binade, the error will either grow or shrink. If you were to look at the sequence of the slopes from binade to binade, you would recognize the repeating digits of 0.1in binary for a while. After that, absorption would start to take place and the curve would go flat.

如果0.1在第四次加法之后继续加法,你最终会观察到舍入误差,这些误差使“0.1加到自身 n 次”偏离n * 0.1,甚至偏离 n/10。如果您将“0.1 添加到自身 n 次”的值绘制为 n 的函数,您将观察到由 binades 组成的恒定斜率线(一旦第 n 次加法的结果注定会落入特定的 binade,可以预期添加的属性与之前在相同组合中产生结果的添加相似)。在同一个 binade 中,误差会增大或缩小。如果您查看从 binade 到 binade 的斜率序列,您会识别出重复的数字0.1在二进制中一段时间​​。之后,吸收将开始发生,曲线将趋于平坦。

回答by DannyB

Floating point systems do various magic including having a few extra bits of precision for rounding. Thus the very small error due to the inexact representation of 0.1 ends up getting rounded off to 0.5.

浮点系统执行各种魔术,包括为舍入增加一些额外的精度。因此,由于 0.1 的不精确表示导致的非常小的误差最终会被四舍五入为 0.5。

Think of floating point as being a great but INEXACT way to represent numbers. Not all possible numbers are easily represented in a computer. Irrational numbers like PI. Or like SQRT(2). (Symbolic math systems can represent them, but I did say "easily".)

将浮点视为表示数​​字的一种很好但不精确的方式。并非所有可能的数字都可以在计算机中轻松表示。PI 等无理数。或者像 SQRT(2)。(符号数学系统可以表示它们,但我确实说“容易”。)

The floating point value may be extremely close, but not exact. It may be so close that you could navigate to Pluto and be off by millimeters. But still not exact in a mathematical sense.

浮点值可能非常接近,但并不准确。它可能离冥王星很近,你可以导航到冥王星,然后相距几毫米。但在数学意义上仍然不准确。

Don't use floating point when you need to be exact rather than approximate. For example, accounting applications want to keep exact track of a certain number of pennies in an account. Integers are good for that because they are exact. The primary issue you need to watch for with integers is overflow.

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

Using BigDecimal for currency works well because the underlying representation is an integer, albeit a big one.

使用 BigDecimal 表示货币效果很好,因为底层表示是一个整数,尽管很大。

Recognizing that floating point numbers are inexact, they still have a great many uses. Coordinate systems for navigation or coordinates in graphics systems. Astronomical values. Scientific values. (You probably cannot know the exact mass of a baseball to within a mass of an electron anyway, so inexactness doesn't really matter.)

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

For counting applications (including accounting) use integer. For counting the number of people that pass through a gate, use int or long.

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