在 Java 17 和 JEP 306 之后,Math 和 StrictMath 之间还有区别吗?

Is there still a difference between Math and StrictMath after Java 17 and JEP 306?

提问人:Joshua Goldberg 提问时间:7/13/2023 更新时间:7/13/2023 访问量:90

问:

在 Java 17 中实现的 JEP 306 提供了始终严格的浮点语义,弃用了该标志。这是否意味着 java.lang.Math 的行为与 StrictMath 中的类似方法完全相同(即,java.lang.Math 方法不能再像过去允许的那样被具有内部函数的 JVM 替换)?这是否也意味着,无论使用哪个库,不同架构之间的浮点数学结果都不应再有任何差异?strictfp

我很好奇我是否误解了 Java 17 的新特性,因为我们确实看到了 Apple Silicon 与 Intel 之间在当今代码中的差异。

浮点 Java-17 StrictFP

评论

2赞 DuncG 7/13/2023
你的例子是什么?
0赞 Joshua Goldberg 7/14/2023
+1,我很欣赏@DuncG,但无法立即回答。我需要挖掘一些,以找到在复杂应用程序中出现差异的子计算。我甚至需要带另一个团队成员,因为我目前只能访问情报。不过,我们确实看到了最终结果的一致差异。
5赞 Holger 7/14/2023
严格和使用内部函数(即专用的 FPU 指令)并不矛盾。这一切都不是使用旧的 x87 FPU,而是 2 年前推出的 SSE2。在 和 参考实现的情况下,自 Java 15 以来就是这种情况。这适用于 HotSpot JVM 以及基于它构建或遵循的所有实现。但只要规范说允许差异,你就必须为它们做好准备......java.lang.Math

答:

2赞 Sergey Tsypanov 12/8/2023 #1

首先,它不是一个标志,这是一个 Java 关键字,用于确保在对浮点变量 ( 和 ) 进行运算时在每个平台上获得相同的计算结果。strictfpfloatdouble

就您的问题而言,OpenJDK 中有两个主要的 PR 涉及 JEP 306 的更改

如果你看一下第一个,特别是 StrictMath,你会看到它现在直接委托给,并且关于语义的注释现在被删除了(以及 OpenJDK 代码库中的关键字)。 此外,从 Java 17 开始,对关键字发出明确的警告(参见:j.l.Mathstrictfpstrictfpjavacstrictfpcompiler.properties

从版本 17 开始,所有浮点表达式都经过严格计算,不是必需的strictfp

所以对于这个问题

这是否意味着 java.lang.Math 的行为与 StrictMath 中的类似方法完全相同

答案是“是”,对于某些方法,例如 委托给(稍后委托给 ),例如方法委托给 。最终,对于浮点变量,是否调用 或 的方法并不重要,相同的代码在后台执行。Math.sinh(double)StrictMath.sinh(double)FdLibm.Sinh.compute(dobule)StrictMath.toRadians(double)Math.toRadians(double)MathStrictMath

截至同一问题的这一部分:

也就是说,java.lang.Math 方法不能再像过去那样被 JVM 替换为内部函数

PR 不会对特定于平台并由 JVM 在运行时应用的内部函数进行任何更改,无论操作是否严格。此外,正如@Holger所指出的,严格性和内在性之间并不矛盾。

这是否也意味着,无论使用哪个库,不同架构之间的浮点数学结果都不应再有任何差异?

答案是“是的,从 Java 17 或更高版本开始”。在此之前,结果可能会有所不同,请参阅 https://stackoverflow.com/a/22335100/12473843。在 Java 17 之前,如果对浮点变量的运算并不严格,则标准假设

在独占使用浮点值集或双精度值集可能导致溢出或下溢的情况下,计算可能会产生“正确答案”。 但是,只要现在语义总是严格的,你就会在所有平台上得到相同的结果。

另请参阅 8268224:清理核心库注释中对“strictfp”的引用

附言

我们确实看到了 Apple Silicon 与 Intel 之间在当今代码中的差异

我认为编译器为不同的平台生成不同的代码是很自然的,这里的问题是你是否在上述平台上看到不同的计算结果。

评论

0赞 Joshua Goldberg 12/11/2023
谢谢!回复最后一行,我们在计算结果的低阶位中得到了不同的结果,而不仅仅是不同的代码生成。我们提出的一个假设是,正在使用的库可能位于 java17 之前编译的 jar 中,但我们无法弄清楚这是否是正在发生的事情。我们尝试过的小表达式在各个架构中都给出了相同的结果,因此除了应用程序中端到端的案例之外,我们没有可重现案例 (MCVE)。
0赞 Sergey Tsypanov 12/13/2023
@JoshuaGoldberg还有一个问题:计算结果是NaN还是一些“真实”值?
1赞 Joshua Goldberg 12/14/2023
真正的价值,不涉及 NaN。