Java 17 与 Java 8 双重表示

Java 17 vs Java 8 double representation

提问人:omar mahameed 提问时间:10/26/2023 最后编辑:Mark Rotteveelomar mahameed 更新时间:10/26/2023 访问量:345

问:

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

那是因为浮点吗?还是在 2 个版本之间有其他变化?

爪哇 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.7399999999999995

爪哇 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

Java 浮点 JVM 精度

评论

5赞 Jon Skeet 10/26/2023
在我看来,版本之间完全有可能(或一般的流)的实现发生了变化。我会尝试在没有任何其他 API 的情况下重现该问题 - 只需使用简单的操作。averagedouble
0赞 chux - Reinstate Monica 10/27/2023
有趣的是,在 12 个不同的加法阶中,结果为 39.7399999999999995...(3/12) 和 39.740000000000002...(9/12)发生。

答:

16赞 Holger 10/26/2023 #1

相关问题是 JDK-8214761:并行 Kahan 求和实现中的错误

由于此错误报告中也提到了它,因此我们可以构建一个消除所有其他影响的示例: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

它与修复程序的向后移植版本匹配。

通常,该方法的约定说结果不必与仅将值相加并除以大小的结果匹配。实现可以自由地提供纠错,但同样重要的是要记住,浮点加法不是严格意义上的关联,但我们必须将其视为关联才能支持并行处理。


我们甚至可以验证该更改是否是一种改进:

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

请注意,这是打印的十进制表示的错误。如果您想知道实际表示与正确值的接近程度,则必须替换为 。然后,误差之间的差异要小一些,但是,新算法更接近两者的正确值。doubleBigDecimal.valueOf(average)new BigDecimal(average)