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