为什么更改总和顺序会返回不同的结果?
为什么更改总和顺序会返回不同的结果?
23.53 + 5.88 + 17.64
= 47.05
23.53 + 17.64 + 5.88
= 47.050000000000004
Java 和 JavaScript 都返回相同的结果。
我理解,由于浮点数以二进制表示的方式,一些有理数(如1/3 - 0.333333...)无法精确表示。
为什么简单地改变元素的顺序会影响结果?
为什么更改总和顺序会返回不同的结果?
23.53 + 5.88 + 17.64
= 47.05
23.53 + 17.64 + 5.88
= 47.050000000000004
Java 和 JavaScript 都返回相同的结果。
我理解,由于浮点数以二进制表示的方式,一些有理数(如1/3 - 0.333333...)无法精确表示。
为什么简单地改变元素的顺序会影响结果?
也许这个问题是愚蠢的,但是为什么简单地改变元素的顺序会影响结果呢?
它将根据值的大小更改值的四舍五入点。作为我们看到的那种事情的一个例子,让我们假设我们使用的是具有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)
我们甚至不需要非整数来解决这个问题:
10000 + 1 - 10000 = (10000 + 1) - 10000
= 10000 - 10000 (where 10001 is rounded to 10000)
= 0
10000 - 10000 + 1 = (10000 - 10000) + 1
= 0 + 1
= 1
这可能更清楚地表明,重要的部分是我们有有限数量的有效数字 - 而不是有限数量的小数位数。如果我们总是可以保持相同数量的小数位数,那么至少通过加法和减法,我们就可以了(只要值不会溢出)。问题是,当您获得较大的数字时,较小的信息会丢失 - 在这种情况下,10001被舍入为10000。(这是埃里克·利珀特(Eric Lippert)在他的答案中指出的问题的一个例子。
请务必注意,右侧第一行的值在所有情况下都是相同的 - 因此,尽管重要的是要了解您的十进制数(23.53,5.88,17.64)不会完全表示为值,但由于上面显示的问题,这只是一个问题。double
以下是二进制文件中发生的事情。众所周知,某些浮点值不能用二进制值精确表示,即使它们可以用十进制精确表示。这3个数字只是这个事实的例子。
使用此程序,我输出每个数字的十六进制表示形式和每个加法的结果。
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);
}
}
该方法只是一个十六进制打印机帮助程序。printValueAndInHex
输出如下:
403787ae147ae148: 23.53
4017851eb851eb85: 5.88
4031a3d70a3d70a4: 17.64
4047866666666666: 47.05
--------
403d68f5c28f5c29: 29.41
4047866666666666: 47.05
--------
404495c28f5c28f6: 41.17
4047866666666667: 47.050000000000004
前 4 个数字是 、 、 和 的十六进制表示形式。在 IEEE 浮点表示中,位 2-12 表示二进制指数,即数字的刻度。(第一位是符号位,其余位是尾数。表示的指数实际上是减去 1023 的二进制数。x
y
z
s
提取前 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
第一组新增内容
第二个数字()的量级较小。当将这两个数字相加得到时,第二个数字()的最后2位移出范围,并且不计入计算。y
x + y
01
第二个加法将两个相同比例的数字相加和相加。x + y
z
第二组新增内容
在这里,首先发生。它们具有相同的比例,但它们产生的数字在规模上更高:x + z
404 => 0|100 0000 0100| => 1028 - 1023 = 5
第二个加法加法和,现在从中删除3位以除以除以数字()。在这里,必须有一个向上舍入,因为结果是下一个浮点数向上:对于第一组加法 vs。 对于第二组添加。该误差足以显示在总数的打印输出中。x + z
y
y
101
4047866666666666
4047866666666667
总之,在对 IEEE 号码执行数学运算时要小心。有些表示是不精确的,当比例不同时,它们变得更加不精确。如果可以的话,加减类似比例的数字。