Java中Stream的mapToDouble.sum()结果随输入顺序不同出现误差的原因
public static void main(String[] args) { double sum=860.10+1808.09; double sum1=1808.09+860.10; System.out.println(sum);// 输出 2668.19 System.out.println(sum1);// 输出 2668.19 List<Double> lst=new ArrayList<Double>(); lst.add(860.10); lst.add(1808.09); Double res=lst.stream().mapToDouble(d->d).sum(); System.out.println(res);// 输出 2668.1900000000005 lst.clear(); // 倒序添加元素 lst.add(1808.09); lst.add(860.10); res=lst.stream().mapToDouble(d->d).sum(); System.out.println(res);// 输出 2668.19 }
问题解答
1. 浮点数本身的精度限制
double是基于IEEE 754标准的二进制浮点数,无法精确表示所有十进制小数。你代码中的860.10、1808.09转换为二进制存储时本身就存在微小的舍入误差,并非完全等于你写的十进制字面量值,这是所有误差出现的基础前提。
2. 双数直接相加顺序不影响的原因
两个double值直接相加时,仅执行一次加法运算,二进制浮点数的单次加法满足交换律,交换两个操作数的位置不会改变运算结果,因此你直接计算的两个sum值完全一致。
3. stream求和顺序影响结果的原因
Java的DoubleStream.sum() 为了降低多元素累加时的累计误差,没有采用简单的两两累加逻辑,而是实现了带误差补偿的Kahan求和算法:算法会额外维护一个补偿变量,用来抵消每次累加产生的微小误差,这个算法的计算过程高度依赖元素的累加顺序。
你这个测试场景下:
- 先加较小的
860.10、再加较大的1808.09时,补偿项的计算结果刚好保留了底层的舍入误差,最终输出就出现了2668.1900000000005的偏差 - 反过来先加较大的
1808.09、再加较小的860.10时,补偿项刚好抵消了底层的舍入误差,最终输出就和直接计算的结果一致
4. 解决方案
如果业务要求精确的十进制运算,不要使用float、double这类二进制浮点数,改为使用BigDecimal类型存储和计算数值即可完全避免这类精度问题。
内容的提问来源于stack exchange,提问作者Chenna
相关产品推荐
相关产品推荐

