Java 为什么更改求和顺序会返回不同的结果?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19820297/
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
Why does changing the sum order returns a different result?
提问by Marlon Bernardes
Why does changing the sum order returns a different result?
为什么更改求和顺序会返回不同的结果?
23.53 + 5.88 + 17.64
=47.05
23.53 + 5.88 + 17.64
=47.05
23.53 + 17.64 + 5.88
=47.050000000000004
23.53 + 17.64 + 5.88
=47.050000000000004
Both Javaand JavaScriptreturn the same results.
双方的Java和JavaScript的返回相同的结果。
I understand that, due to the way floating point numbers are represented in binary, some rational numbers (like 1/3 - 0.333333...) cannot be represented precisely.
我明白,由于浮点数以二进制表示的方式,一些有理数(如 1/3 - 0.333333...)无法精确表示。
Why does simply changing the order of the elements affect the result?
为什么简单地改变元素的顺序会影响结果?
采纳答案by Jon Skeet
Maybe this question is stupid, but why does simply changing the order of the elements affects the result?
也许这个问题很愚蠢,但为什么简单地改变元素的顺序会影响结果?
It will change the points at which the values are rounded, based on their magnitude. As an example of the kindof thing that we're seeing, let's pretend that instead of binary floating point, we were using a decimal floating point type with 4 significant digits, where each addition is performed at "infinite" precision and then rounded to the nearest representable number. Here are two sums:
它将根据值的大小更改四舍五入的点。作为我们所看到的那种事情的一个例子,让我们假设我们使用的是具有 4 个有效数字的十进制浮点类型而不是二进制浮点数,其中每个加法都以“无限”精度执行,然后四舍五入为最接近的可表示数字。这里有两个总和:
1/3 + 2/3 + 2/3 = (0.3333 + 0.6667) + 0.6667
= 1.000 + 0.6667 (no rounding needed!)
= 1.667 (where 1.6667 is rounded to 1.667)
2/3 + 2/3 + 1/3 = (0.6667 + 0.6667) + 0.3333
= 1.333 + 0.3333 (where 1.3334 is rounded to 1.333)
= 1.666 (where 1.6663 is rounded to 1.666)
We don't even need non-integers for this to be a problem:
我们甚至不需要非整数来解决这个问题:
10000 + 1 - 10000 = (10000 + 1) - 10000
= 10000 - 10000 (where 10001 is rounded to 10000)
= 0
10000 - 10000 + 1 = (10000 - 10000) + 1
= 0 + 1
= 1
This demonstrates possibly more clearly that the important part is that we have a limited number of significant digits- not a limited number of decimal places. If we could always keep the same number of decimal places, then with addition and subtraction at least, we'd be fine (so long as the values didn't overflow). The problem is that when you get to bigger numbers, smaller information is lost - the 10001 being rounded to 10000 in this case. (This is an example of the problem that Eric Lippert noted in his answer.)
这可能更清楚地表明,重要的部分是我们有有限数量的有效数字——而不是有限数量的小数位。如果我们可以始终保持相同的小数位数,那么至少通过加法和减法,我们就可以了(只要值不溢出)。问题是当你得到更大的数字时,会丢失更小的信息——在这种情况下,10001 被四舍五入为 10000。(这是Eric Lippert 在他的回答中指出的问题的一个例子。)
It's important to note that the values on the first line of the right hand side are the same in all cases - so although it's important to understand that your decimal numbers (23.53, 5.88, 17.64) won't be represented exactly as double
values, that's only a problem because of the problems shown above.
请务必注意,右侧第一行的值在所有情况下都相同 - 因此,尽管了解您的十进制数 (23.53, 5.88, 17.64) 不会完全表示为double
值很重要,但是由于上面显示的问题,只是一个问题。
回答by hotforfeature
I believe it has to do with the order of evaulation. While the sum is naturally the same in a math world, in the binary world instead of A + B + C = D, it's
我认为这与评估的顺序有关。虽然总和在数学世界中自然是相同的,但在二进制世界中而不是 A + B + C = D,它是
A + B = E
E + C = D(1)
So there's that secondary step where floating point numbers can get off.
所以有浮点数可以下车的第二步。
When you change the order,
当您更改订单时,
A + C = F
F + B = D(2)
回答by jbx
Floating point numbers are represented using the IEEE 754 format, which provides a specific size of bits for the mantissa (significand). Unfortunately this gives you a specific number of 'fractional building blocks' to play with, and certain fractional values cannot be represented precisely.
浮点数使用 IEEE 754 格式表示,该格式为尾数(有效数)提供特定大小的位。不幸的是,这为您提供了特定数量的“分数积木”,并且某些分数无法精确表示。
What is happening in your case is that in the second case, the addition is probably running into some precision issue because of the order the additions are evaluated. I haven't calculated the values, but it could be for example that 23.53 + 17.64 cannot be precisely represented, while 23.53 + 5.88 can.
在您的情况下发生的情况是,在第二种情况下,由于评估添加的顺序,添加可能会遇到一些精度问题。我没有计算过这些值,但可能是例如 23.53 + 17.64 无法精确表示,而 23.53 + 5.88 可以。
Unfortunately it is a known problem that you just have to deal with.
不幸的是,这是一个您必须处理的已知问题。
回答by Compass
This actually covers much more than just Java and Javascript, and would likely affect any programming language using floats or doubles.
这实际上涵盖的不仅仅是 Java 和 Javascript,并且可能会影响使用浮点数或双精度数的任何编程语言。
In memory, floating points use a special format along the lines of IEEE 754 (the converter provides much better explanation than I can).
在内存中,浮点使用符合 IEEE 754 的特殊格式(转换器提供的解释比我能做的要好得多)。
Anyways, here's the float converter.
无论如何,这是浮点转换器。
http://www.h-schmidt.net/FloatConverter/
http://www.h-schmidt.net/FloatConverter/
The thing about the order of operations is the "fineness" of the operation.
关于操作顺序的事情是操作的“精细度”。
Your first line yields 29.41 from the first two values, which gives us 2^4 as the exponent.
您的第一行从前两个值中得出 29.41,这给了我们 2^4 作为指数。
Your second line yields 41.17 which gives us 2^5 as the exponent.
你的第二行产生 41.17,这给了我们 2^5 作为指数。
We're losing a significant figure by increasing the exponent, which is likely to change the outcome.
通过增加指数,我们正在失去一个重要的数字,这可能会改变结果。
Try ticking the last bit on the far right on and off for 41.17 and you can see that something as "insignificant" as 1/2^23 of the exponent would be enough to cause this floating point difference.
尝试为 41.17 打开和关闭最右侧的最后一位,您会看到指数的 1/2^23 等“无关紧要”的东西足以导致这种浮点差异。
Edit: For those of you who remember significant figures, this would fall under that category. 10^4 + 4999 with a significant figure of 1 is going to be 10^4. In this case, the significant figure is much smaller, but we can see the results with the .00000000004 attached to it.
编辑:对于那些记得重要人物的人,这将属于该类别。10^4 + 4999 的有效数字为 1 将是 10^4。在这种情况下,有效数字要小得多,但我们可以看到附加了 .00000000004 的结果。
回答by rgettman
Here's what's going on in binary. As we know, some floating-point values cannot be represented exactly in binary, even if they can be represented exactly in decimal. These 3 numbers are just examples of that fact.
这是二进制文件中发生的事情。正如我们所知,一些浮点值不能用二进制精确表示,即使它们可以用十进制精确表示。这三个数字只是这个事实的例子。
With this program I output the hexadecimal representations of each number and the results of each addition.
通过这个程序,我输出每个数字的十六进制表示和每个加法的结果。
public class Main{
public static void main(String args[]) {
double x = 23.53; // Inexact representation
double y = 5.88; // Inexact representation
double z = 17.64; // Inexact representation
double s = 47.05; // What math tells us the sum should be; still inexact
printValueAndInHex(x);
printValueAndInHex(y);
printValueAndInHex(z);
printValueAndInHex(s);
System.out.println("--------");
double t1 = x + y;
printValueAndInHex(t1);
t1 = t1 + z;
printValueAndInHex(t1);
System.out.println("--------");
double t2 = x + z;
printValueAndInHex(t2);
t2 = t2 + y;
printValueAndInHex(t2);
}
private static void printValueAndInHex(double d)
{
System.out.println(Long.toHexString(Double.doubleToLongBits(d)) + ": " + d);
}
}
The printValueAndInHex
method is just a hex-printer helper.
该printValueAndInHex
方法只是一个十六进制打印机助手。
The output is as follows:
输出如下:
403787ae147ae148: 23.53
4017851eb851eb85: 5.88
4031a3d70a3d70a4: 17.64
4047866666666666: 47.05
--------
403d68f5c28f5c29: 29.41
4047866666666666: 47.05
--------
404495c28f5c28f6: 41.17
4047866666666667: 47.050000000000004
The first 4 numbers are x
, y
, z
, and s
's hexadecimal representations. In IEEE floating point representation, bits 2-12 represent the binary exponent, that is, the scale of the number. (The first bit is the sign bit, and the remaining bits for the mantissa.) The exponent represented is actually the binary number minus 1023.
第4个数字是x
,y
,z
,和s
的十六进制表示。在 IEEE 浮点表示中,第 2-12 位表示二进制指数,即数字的小数位数。(第一位是符号位,其余位是尾数。)表示的指数实际上是二进制数减去 1023。
The exponents for the first 4 numbers are extracted:
提取前 4 个数字的指数:
sign|exponent
403 => 0|100 0000 0011| => 1027 - 1023 = 4
401 => 0|100 0000 0001| => 1025 - 1023 = 2
403 => 0|100 0000 0011| => 1027 - 1023 = 4
404 => 0|100 0000 0100| => 1028 - 1023 = 5
First set of additions
第一组补充
The second number (y
) is of smaller magnitude. When adding these two numbers to get x + y
, the last 2 bits of the second number (01
) are shifted out of range and do not figure into the calculation.
第二个数字 ( y
) 的幅度较小。将这两个数字相加得到 时x + y
,第二个数字 ( 01
)的最后 2 位被移出范围并且不计入计算。
The second addition adds x + y
and z
and adds two numbers of the same scale.
第二个加法将x + y
和z
和加上两个相同比例的数字。
Second set of additions
第二组补充
Here, x + z
occurs first. They are of the same scale, but they yield a number that is higher up in scale:
在这里,x + z
首先发生。它们具有相同的比例,但它们产生的数字在比例上更高:
404 => 0|100 0000 0100| => 1028 - 1023 = 5
The second addition adds x + z
and y
, and now 3bits are dropped from y
to add the numbers (101
). Here, there must be a round upwards, because the result is the next floating point number up: 4047866666666666
for the first set of additions vs. 4047866666666667
for the second set of additions. That error is significant enough to show in the printout of the total.
第二次加法加上x + z
和y
,现在去掉3位y
来加上数字 ( 101
)。在这里,必须向上舍入,因为结果是下一个向上的浮点数:4047866666666666
第一组加法与4047866666666667
第二组加法。该错误足以显示在总数的打印输出中。
In conclusion, be careful when performing mathematical operations on IEEE numbers. Some representations are inexact, and they become even more inexact when the scales are different. Add and subtract numbers of similar scale if you can.
总之,对 IEEE 数字执行数学运算时要小心。有些表示是不精确的,当尺度不同时,它们变得更加不精确。如果可以,添加和减去类似比例的数字。
回答by Eric Lippert
Jon's answer is of course correct. In your case the error is no larger than the error you would accumulate doing any simple floating point operation. You've got a scenario where in one case you get zero error and in another you get a tiny error; that's not actually that interesting a scenario. A good question is: are there scenarios where changing the order of calculations goes from a tiny error to a (relatively) enormous error?The answer is unambiguously yes.
乔恩的回答当然是正确的。在您的情况下,误差不大于您在执行任何简单浮点运算时累积的误差。你有一个场景,在一种情况下你得到零错误,而在另一种情况下你得到一个小错误;这实际上并不是一个有趣的场景。一个很好的问题是:是否存在将计算顺序从微小错误变为(相对)巨大错误的场景?答案毫无疑问是肯定的。
Consider for example:
考虑例如:
x1 = (a - b) + (c - d) + (e - f) + (g - h);
vs
对比
x2 = (a + c + e + g) - (b + d + f + h);
vs
对比
x3 = a - b + c - d + e - f + g - h;
Obviously in exact arithmetic they would be the same. It is entertaining to try to find values for a, b, c, d, e, f, g, h such that the values of x1 and x2 and x3 differ by a large quantity. See if you can do so!
显然,在精确的算术中,它们是相同的。尝试找到 a、b、c、d、e、f、g、h 的值使得 x1、x2 和 x3 的值相差很大是很有趣的。看看你能不能做到!