Java中double加减相同数值为何出现精度误差?
为什么double运算中(d1+d2-d2-d1)会产生精度误差?
由于浮点数精度限制,Java的double类型无法精确表示所有十进制数,会引入精度误差。给定如下代码:
double d1=0.20976190476190476; double d2=0.062142857142857146; System.out.println("d1-d1= "+(d1-d1)); //输出0.0。 System.out.println("d1+d2-d2-d1= "+(d1+d2-d2-d1)); //输出2.7755575615628914E-17
疑问:为何最后一行会产生精度误差?原以为表达式会按((d1+d2)-d2)-d1的顺序计算,即便d1、d2或d1+d2无法精确表示,精度误差也会相互抵消?
原因解析
浮点数的本质是二进制近似:
double类型采用IEEE 754标准存储,只能精确表示有限的十进制数。你定义的d1和d2的十进制值转成二进制是无限循环的,所以实际存储的是它们最接近的近似二进制浮点数(记为D1和D2),并非原始十进制值。每一步运算都可能引入舍入误差:
- 第一步计算
D1+D2:两个近似值相加的结果,会被舍入到最接近的double值(记为S),这里已经引入了一次舍入误差ε1,即S = D1+D2 + ε1。 - 第二步计算
S-D2:此时结果是D1 + ε1,但这个值不一定能被double精确表示,会再次被舍入到最接近的double值(记为D1'),又引入了二次舍入误差ε2,即D1' = D1 + ε1 + ε2。 - 第三步计算
D1' - D1:结果就是ε1+ε2,也就是你看到的2.7755575615628914E-17。
- 第一步计算
为什么
d1-d1是0.0?
IEEE 754标准规定,相同的double值相减结果精确为0.0,因为操作数完全一致,没有舍入差异,所以误差直接抵消。但(d1+d2)-d2得到的并不是原始的d1近似值,两次舍入的误差无法完全抵消,最终就出现了微小的差值。
内容的提问来源于stack exchange,提问作者Joris Kinable
相关产品推荐
相关产品推荐

