在两个不同的JVM(Java 8和Java 17)之间求平均值时,出现相同值差异的原因是什么?

是不是因为浮点数?或者在两个版本之间发生了其他变化?

Java 17

public class Main {
    public static void main(String[] args) {

        List<Double> amountList = List.of(27.19, 18.97, 6.44, 106.36);

        System.out.println("JAVA 17 result: " + amountList.stream().mapToDouble(x -> x).average().orElseThrow());

    }
}

结果:39.7399999999995

Java 8

public class Main {

    public static void main(String[] args) {

        List<Double> amountList = Arrays.asList(27.19, 18.97, 6.44, 106.36);

        System.out.println("JAVA 8 result: " + amountList.stream().mapToDouble(x -> x).average().orElse(0.0));
    }
}

结果:39.74000000000001

推荐答案

相关问题是JDK-8214761: Bug in parallel Kahan summation implementation

由于此错误报告中提到DoubleSummaryStatistics也受到影响,因此我们可以构造一个消除所有其他影响的示例:

public class Main {
    public static void main(String[] args) {
      DoubleSummaryStatistics s = new DoubleSummaryStatistics();
      s.accept(27.19);
      s.accept(18.97);
      s.accept(6.44);
      s.accept(106.36);
      System.out.println(System.getProperty("java.version")+": "+s.getAverage());
    }
}

我曾经制作过的

1.8.0_162: 39.74000000000001
17: 39.74000000000001

(与Java 17的发布版本一起)

17.0.2: 39.739999999999995

backport of the fix的版本相符

Generally, the contract of the method says that the result does not have to match the result of just adding the values 和 dividing by the size. There’s the implementation’s freedom to provide an error correction but it’s also important to keep in mind that floating point addition is not strictly associative but we have to treat it as associative to be able to support parallel processing.


我们甚至可以证实,这一变化是一种改进:

DoubleSummaryStatistics s = new DoubleSummaryStatistics();
s.accept(27.19);
s.accept(18.97);
s.accept(6.44);
s.accept(106.36);
double average = s.getAverage();
System.out.println(System.getProperty("java.version") + ": " + average);

BigDecimal d = new BigDecimal("27.19");
d = d.add(new BigDecimal("18.97"));
d = d.add(new BigDecimal("6.44"));
d = d.add(new BigDecimal("106.36"));

BigDecimal realAverage = d.divide(BigDecimal.valueOf(4), MathContext.UNLIMITED);
System.out.println("actual: " + realAverage
        + ", error: " + realAverage.subtract(BigDecimal.valueOf(average)).abs());

哪个打印,例如

1.8.0_162: 39.74000000000001
actual: 39.74, error: 1E-14
17.0.2: 39.739999999999995
actual: 39.74, error: 5E-15

请注意,这是打印的十进制表示法的错误.如果您想知道实际的double表示与正确值的距离有多近,则必须用new BigDecimal(average)替换BigDecimal.valueOf(average).然后,误差之间的差异会稍微小一些,然而,新算法更接近两者的正确值.

Java相关问答推荐

使用Apache Poi MQLSlideShow,在XSLFTable表中,我们可以在文本段落后面的每个单元格中包含圆角矩形吗?

如果给定层次 struct 级别,如何从其预序穿越构造n元树

@ EnableRouting注释在Kotlin项目中不工作

我想了解Java中的模块化.编译我的应用程序时,我有一个ResolutionException

如何打印本系列的第n项y=-(1)-(1+2)+(1+2+3)+(1+2+3+4)-(1+2+3+4+5)...Java中的(1+2+3+4...+n)

如何调整工作时间日历中的时间

我正在try 跟踪数组中最大的两个数字

SpringBoot:在条件{Variable}.isBlank/{Variable}.isEmpty不起作用的情况下进行路径变量验证

带有可选部分的Java DateTimeForMatter

JFree Chart从图表中删除边框

在Spring Boot JPA for MySQL中为我的所有类创建Bean时出错?

循环不起作用只有第一个元素重复

Android无法在Java代码中调用Kotlin代码,原因是在Companion中使用Kotlin枚举时

在权限列表中找不到我的应用程序

如何利用OpenTelemeter将初始值(零)输出到普罗米修斯

如何使用带有可选参数的类生成器?

";home/runner/work/中没有文件...匹配到[**/pom.xml];Maven项目的构建过程中出现错误

保持标题窗格的箭头可见,即使设置为不可折叠

如何以事务方式向ibmmq发送消息

为什么child-pom会创建一个新版本