这是二进制文件中发生的事情。众所周知,一些浮点值不能用二进制精确表示,即使它们可以用十进制精确表示。这 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 个数字是 x、y、z 和 s 的十六进制表示。在 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 时,第二个数字(01)的最后 2 位被移出范围,不计入计算。
第二次加法将x + y 和z 相加,并将两个相同比例的数字相加。
第二组补充
在这里,x + z 首先出现。它们具有相同的规模,但它们产生的数字更高:
404 => 0|100 0000 0100| => 1028 - 1023 = 5
第二个添加添加了x + z 和y,现在从y 中删除了 3 位以添加数字 (101)。这里必须向上取整,因为结果是下一个浮点数向上:4047866666666666 表示第一组加法,4047866666666667 表示第二组加法。该错误非常严重,足以显示在总数的打印输出中。
总之,在对 IEEE 数字进行数学运算时要小心。有些表示是不精确的,当尺度不同时,它们变得更加不精确。如果可以的话,加减类似比例的数字。