Java 17 与 Java 8 双重表示

oma*_*eed 19 java floating-point precision jvm

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

是因为浮点吗?或者两个版本之间还有其他变化吗?

爪哇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());

    }
}
Run Code Online (Sandbox Code Playgroud)

结果:39.739999999999995

爪哇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));
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:39.74000000000001

Hol*_*ger 16

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

\n

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

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

我曾经生产过

\n
1.8.0_162: 39.74000000000001\n
Run Code Online (Sandbox Code Playgroud)\n
17: 39.74000000000001\n
Run Code Online (Sandbox Code Playgroud)\n

(发行版本为Java\xc2\xa017)

\n

\n
17.0.2: 39.739999999999995\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

一般来说,该方法的约定表明结果不必与仅将值相加并除以大小的结果相匹配。\xe2\x80\x99s 的实现\xe2\x80\x99s 提供纠错的自由,但 \xe2\x80\x99s 也很重要,要记住浮点加法不是严格关联的,但我们必须将其视为关联能够支持并行处理。

\n
\n

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

\n
DoubleSummaryStatistics s = new DoubleSummaryStatistics();\ns.accept(27.19);\ns.accept(18.97);\ns.accept(6.44);\ns.accept(106.36);\ndouble average = s.getAverage();\nSystem.out.println(System.getProperty("java.version") + ": " + average);\n\nBigDecimal d = new BigDecimal("27.19");\nd = d.add(new BigDecimal("18.97"));\nd = d.add(new BigDecimal("6.44"));\nd = d.add(new BigDecimal("106.36"));\n\nBigDecimal realAverage = d.divide(BigDecimal.valueOf(4), MathContext.UNLIMITED);\nSystem.out.println("actual: " + realAverage\n        + ", error: " + realAverage.subtract(BigDecimal.valueOf(average)).abs());\n
Run Code Online (Sandbox Code Playgroud)\n

打印,例如

\n
1.8.0_162: 39.74000000000001\nactual: 39.74, error: 1E-14\n
Run Code Online (Sandbox Code Playgroud)\n
17.0.2: 39.739999999999995\nactual: 39.74, error: 5E-15\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n