Java 浮点计算行为差异

gre*_*reg 5 java precision jit java-8 java-17

在用 Java 实现的科学计算管道的背景下,我们使用 Apache Commons math 3.6.1 中的 LeastSquare 优化器,并体验到 Java 8 (jdk8u322-b06)、Java 和 Java 17 之间奇怪的数值差异。

我们注意到迁移到 Java 17(当前使用 17.0.5)后的差异。

我可以在“简单”单元测试中重现 Java 8 和 Java 17 之间的不同行为,而无需涉及完整的计算管道,因此排除了数据加载或多线程部分可能出现的所有问题。

在 JDK 17 中,严格的浮点语义已恢复,但结果与编译或解释的 Java 8 完全不同。在这种情况下,实际上最小二乘优化器不会像在 Java 8 情况下那样收敛。

重要的一点是,所有这些结果都可以完全重现到最后一位数字,并且在每个平台配置上都是完全可预测的。

但这次在完整的管道执行中,我观察到我可以在 Java 8 本身上重现数值差异,具体取决于代码是解释还是编译(在 C2 级别或 C1 级别我尚未调查)

我真的很困惑,想知道这是否是已知/记录的行为,或者这是否是 JDK 8 平台或 JDK 17 中的错误?有人观察到这样的问题吗?

感谢您的任何提示!

编辑

我无法在这里提供简单的代码片段来重现该问题。但我提取了代码的必要部分以在 GitHub 存储库中重现该行为。免责声明:我不是代码的原作者。

重现步骤:

要查看行为差异,只需将 pom.xml 中的 java.version 属性从 1.8 切换到 17。

我可以查明 Apache math commons 中代码分歧的位置:在 Apache Math commons 3.6.1 的 LevenbergMarquardtOptimizer 类中(第 453 行)

  double actRed = -1.0;
      if (0.1 * currentCost < previousCost) {
          double r = currentCost / previousCost;
          actRed = 1.0 - r * r;
      }
Run Code Online (Sandbox Code Playgroud)

在迭代 17 时,actRed 变为 0,因为在 Java 17 中 r==1,而在 Java 8 中则稍微为正,因为值为 0,稍后(第 533 行)抛出非收敛异常

 if (FastMath.abs(actRed) <= TWO_EPS &&
     preRed <= TWO_EPS && ratio <= 2.0) {
         throw new ConvergenceException(LocalizedFormats.TOO_SMALL_COST_RELATIVE_TOLERANCE,
                                               costRelativeTolerance);
            } 
Run Code Online (Sandbox Code Playgroud)