为什么更改总和顺序会返回不同的结果?
23.53 + 5.88 + 17.64
= 47.05
23.53 + 17.64 + 5.88
= 47.050000000000004
Java 和 JavaScript 返回相同的结果。
我知道,由于浮点数以二进制表示的方式,一些有理数( 如 1⁄3 - 0.333333… )无法精确表示。
为什么简单地改变元素的顺序会影响结果?
原文由 Marlon Bernardes 发布,翻译遵循 CC BY-SA 4.0 许可协议
为什么更改总和顺序会返回不同的结果?
23.53 + 5.88 + 17.64
= 47.05
23.53 + 17.64 + 5.88
= 47.050000000000004
Java 和 JavaScript 返回相同的结果。
我知道,由于浮点数以二进制表示的方式,一些有理数( 如 1⁄3 - 0.333333… )无法精确表示。
为什么简单地改变元素的顺序会影响结果?
原文由 Marlon Bernardes 发布,翻译遵循 CC BY-SA 4.0 许可协议
这是二进制文件中发生的事情。正如我们所知,有些浮点值不能精确地用二进制表示,即使它们可以精确地用十进制表示。这 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
The first 4 numbers are x
, y
, z
, and s
’s hexadecimal representations.在 IEEE 浮点表示法中,第 2-12 位表示二进制 _指数_,即数字的小数位数。 (第一位为符号位,其余位为 _尾数_。)表示的指数实际上是二进制数负1023。
提取前 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
第一组补充
第二个数字 ( y
) 较小。当将这两个数字相加得到 x + y
时,第二个数字的最后 2 位 ( 01
) 被移出范围并且不计算在内。
第二次添加添加 x + y
和 z
并添加相同比例的两个数字。
第二组补充
在这里, x + z
首先出现。它们具有相同的规模,但它们产生的数字规模更高:
404 => 0|100 0000 0100| => 1028 - 1023 = 5
The second addition adds x + z
and y
, and now 3 bits are dropped from y
to add the numbers ( 101
).在这里,必须有一个向上的循环,因为结果是下一个向上的浮点数: 4047866666666666
第一组加法与 4047866666666667
第二组加法。该错误足以显示在总数的打印输出中。
总之,在对 IEEE 数字执行数学运算时要小心。有些表示是不精确的,当尺度不同时它们变得更加不精确。如果可以的话,加减类似比例的数字。
原文由 rgettman 发布,翻译遵循 CC BY-SA 3.0 许可协议
13 回答13k 阅读
7 回答2.2k 阅读
4 回答1.6k 阅读✓ 已解决
4 回答1.3k 阅读✓ 已解决
3 回答1.3k 阅读✓ 已解决
6 回答1.3k 阅读✓ 已解决
2 回答1.4k 阅读✓ 已解决
它将根据值的大小更改值四舍五入的点。作为我们所看到的 这种 情况的一个例子,让我们假设我们使用的不是二进制浮点,而是具有 4 个有效数字的十进制浮点类型,其中每次加法都以“无限”精度执行,然后四舍五入为最接近的可表示数字。这里有两个总和:
我们甚至不需要非整数来解决这个问题:
这可能更清楚地表明重要的部分是我们有有限数量的 _有效数字_——而不是有限数量的 _小数位_。如果我们可以始终保持相同的小数位数,那么至少通过加法和减法就可以了(只要值不溢出)。问题是,当您获得更大的数字时,会丢失更小的信息——在这种情况下,10001 被四舍五入为 10000。 (这是 Eric Lippert 在他的回答中指出 的问题示例。)
重要的是要注意右侧第一行的值在所有情况下都是相同的 - 因此尽管了解您的十进制数字(23.53、5.88、17.64)不会完全表示为
double
值,这只是一个问题,因为上面显示的问题。