在Java中,什么时候应该使用"strictfp"关键字?

283

我查阅了这个关键字的含义,但是有人能否给一个使用Java中的 strictfp 关键字的例子?有人真的用过这个吗?

在所有我的浮点运算操作上都加上它会有什么副作用吗?


1
除非你真正需要性能胜过可重现性,否则请始终如一地保持可重现性。 - Antimony
1
@Antimony - 或者说精度/正确性。例如,x86/x64内部使用80位浮点寄存器,因此在没有strictfp的情况下进行长时间计算时,结果会更准确。 - Robert Fraser
1
@Robert 实际上,规范保证了尾数的有限精度。唯一的区别是它可能使用比正常情况下更大的指数精度,在某些罕见情况下会因为双重舍入而产生差异。 - Antimony
我认为除了在程序中随处使用这个有用的修饰符之外,引入新的sfloat和sdouble原始strictfp数据类型可能是一个好主意。 - theRiley
除非你在编写科学计算器,否则在生产代码中必须使用never。 - Arefe
11个回答

293

Strictfp确保你的浮点数计算在每个平台上都能得到完全相同的结果。如果你不使用strictfp,JVM实现可以在可用的情况下使用额外的精度。

根据JLS

在FP-strict表达式中,所有中间值必须是float值集或double值集的元素,这意味着所有FP-strict表达式的结果必须与使用单精度和双精度格式表示的操作数在IEEE 754算术上预测的结果相同。在不是FP-strict的表达式中,一些实现允许使用扩展指数范围来表示中间结果;粗略地说,其净效应是,在仅使用float值集或double值集可能导致溢出或下溢的情况下,计算可能产生“正确的答案”。

换句话说,这就是确保一次编写,到处运行实际上意味着一次编写,到处获得完全错误的结果

使用strictfp可以使你的结果具有可移植性,否则可能更准确。


34
将其用于可重复的科学结果和位完全相同的单元测试。 - Aleksandr Dubinsky
2
如果您不使用strictfp,JVM实现可以在可用的情况下使用额外的精度--您让这听起来像是一件坏事:P - AMDG
1
@LinkTheProgrammer,这当然可能是一件坏事。 - Tim
1
@TimCastelijns 我猜 Happy Wheels 是你的参考吧?回放记录按键;由于FP数学实现精度多样性,回放仅在类似硬件上准确。你能说出由浮点数学可变性引起的更现实的问题吗?我可以想象可能是粒子模拟器,但还有什么? - AMDG
5
请注意,自Java 17起,浮点数运算再次始终是严格的,而且不再需要使用strictfp。实际上,当使用它时,编译器会发出警告。 - Moritz
显示剩余3条评论

68

5
“扩展指数范围以表示中间结果”并不意味着“许可以任意计算浮点计算”,实际上,即使使用strictfp 计算,也会使用甚至不太理想的8087 FPU。只需稍微小心一点即可。参见 https://dev59.com/eWMl5IYBdhLWcg3wVlpf - Pascal Cuoq
我同意@PascalCuoq的观点,即“许可计算机以任何方式计算浮点数”。如果有什么不同的话,在这种情况下相反似乎更为真实,因为strictfp确保符合IEEE 754标准(以便在所有平台上获得相同的结果)。我唯一能看到的缺点是您可能会失去在本地硬件中拥有真正好的FPU的好处。 - typeracer
简而言之,当您需要在不同的CPU模型上每次进行相同的浮点计算时,请使用strictfp。请注意,如果您的浮点数不是那么大,则可能会过度使用。 - Georgi Peev

31

Java 17 更新

strictfp 的使用范围非常狭窄。因此,从 Java 17 开始,该功能已被删除。它仍然是一个有效的修饰符,但现在strictfp 不会执行任何操作 (JLS来源)。

相反,在 Java 1.2 引入 strictfp 之前所有浮点数操作都是严格的。在现代处理器上,不再有额外性能成本。


原始回答

以下是几个参考文献:

  • 使用 strictfp (JDC 技巧)

  • jGuru:什么是 strictfp 修饰符?何时应考虑使用它?

    基本上,问题就在于你是否关心代码中浮点表达式的结果是快速还是可预测的。例如,如果你需要你的代码使用浮点数值计算出来的答案在多个平台上保持一致,则使用 strictfp

  • strictfp - Java 词汇表

    浮点硬件使用比Java规范要求更高的精度和更大的值域进行计算。如果一些平台提供比其他平台更高的精度,则会令人困惑。当你在方法或类上使用 strictfp 修饰符时,编译器生成严格遵循Java规范的代码,以在所有平台上产生相同的结果。没有 strictfp,则略微宽松,但不会使用Pentium的守卫位提供80位的精度。

  • 最后是实际的Java语言规范,§15.4 FP-strict 表达式

    在 FP-strict 表达式中,所有中间值必须是 float 值集或 double 值集的元素,这意味着所有 FP-strict 表达式的结果必须是使用单精度和双精度格式表示的操作数在 IEEE 754 算术下预测的结果。在不是 FP-strict 的表达式中,允许实现使用扩展指数范围表示中间结果;粗略地说,这样做会使计算在只使用 float 值集或 double 值集可能导致溢出或下溢情况下产生“正确的答案”。

虽然我个人从未使用过它。


你写道:“我个人从未使用过它。” 我也是一样。有没有人有一个真实世界的例子来说明它的用途? - kevinarpe
1
@kevinarpe 你非常幸运。是的。与主机的对账。没有 strictfp,账本没能完全平衡。这是一个有趣的 bug,需要找到并修复它。 - Elliott Frisch

29

这一切始于一个故事,

当詹姆斯·高斯林(James Gosling)、赫伯特和他们的团队开发Java时。他们有一个疯狂的想法,叫做平台无关性。他们想让oak(Java)变得更好,以便在任何具有不同指令集、甚至运行不同操作系统的机器上都能完全相同地运行。但是,编程语言中的十进制数(也称为浮点数和双精度)存在一个问题。有些机器着眼于效率而另一些则着眼于精确性。 因此,后者(更准确的机器)的浮点大小为80位,而前者(更有效/更快的机器)的双精度为64位。但是,这与他们构建一个平台无关的语言的核心思想相违背。此外,这可能会导致在某台机器(拥有64位双精度)上构建代码并在另一种类型的机器(拥有80位双精度)上运行时出现精度/数据丢失。

可以容忍向上扩展,但不能向下扩展。 因此,他们遇到了一个名为“strictfp”的概念,即严格浮点。如果您在一个类/函数中使用此关键字,则它的浮点数和双精度在任何机器上都具有一致的大小。即32/64位。


9
strictfp 是在 Java 1.2 中引入的,这比 oak 设计时晚得多。 - Thorbjørn Ravn Andersen
1
“十进制小数点数也被称为浮点数”-- 十进制意味着基于10进制,与浮点表示无关。 - aioobe
@aioobe 在美国(我不知道其他讲英语的国家)用于分隔整数和小数部分的符号被称为“小数点”。这就是abhimanyuaryan所指的。我相信你理解了。在这种情况下,没有必要陷入语义学上的纠葛。 - hfontanez
https://english.stackexchange.com/questions/422162/what-is-the-binary-equivalent-to-decimal-and-decimal-point - aioobe

12
正如其他答案所提到的,这会导致中间浮点结果符合IEEE规范。特别是x86处理器可以以不同于IEEE规范的精度存储中间结果。当JIT优化特定计算时,情况变得更加复杂;指令的顺序可能每次都不同,从而导致稍有不同的舍入。
strictfp带来的开销可能非常依赖于处理器和JIT。这篇关于SSE2的维基百科文章似乎对这个问题有一些见解。因此,如果JIT可以生成SSE指令来执行计算,那么strictfp将不会有任何开销。
在我的当前项目中,有几个地方我使用了strictfp。有一个点需要从像素值中去除潜在的宇宙射线。如果某个外部研究人员具有相同的像素值和宇宙射线,则他们应该得到与我们软件相同的结果值。

8
  • strictfp是一种关键字,它按照IEEE 754的规范限制浮点数计算。

  • 可以在整个类中使用,如"public strictfp class StrictFpModifierExample{}",也可以在方法上使用,如"public strictfp void example()"。如果用于类,则所有方法都将遵循IEEE 754;如果用于方法,则特定方法将遵循IEEE 754。

  • 为什么要使用它?因为不同平台有不同的浮点硬件,可以比Java规范要求更精确地计算更大范围的值,这可能会在不同平台上产生不同的输出,而strictfp可以确保相同的输出无论在哪个平台上运行。

  • 使用strictfp还可以利用扩展精度浮点运算的速度和精度。

  • 这个关键字没有任何缺点,我们在进行浮点数计算时可以使用它。

  • 最后一个问题是-什么是IEEE 754? IEEE 754定义了标准方法,用于单精度(32位,用于Java浮点数)或双精度(64位,用于Java双精度)精度的浮点计算和浮点值的存储。它还为中间计算和扩展精度格式定义了规范。


2
strictfp是一个关键字,可用作类或方法的非访问修饰符(但永远不会用于变量)。将类标记为strictfp意味着类中的任何方法代码都将符合IEEE 754浮点规则。如果没有该修饰符,则在方法中使用的浮点数可能以平台相关的方式运行。有了它,您可以预测您的浮点数将如何运行,而不管JVM正在运行的底层平台如何。缺点是,如果底层平台能够支持更高的精度,则strictfp方法将无法利用它。如果您未将类声明为strictfp,仍然可以通过将方法声明为strictfp来逐个方法地获得strictfp行为。

2

从Java 17+开始,strictfp修饰符已经过时并且不起作用。您不再需要使用此修饰符。


0
以下示例可能有助于更清楚地理解: 在Java中,每当我们正在寻找任何操作的精确信息时,例如 如果我们执行double num1 = 10e + 102; double num2 = 8e + 10; 结果= num1 + num2;
        The output will be so long and not precise, becasue it is precissed by the hardware e.g JVM and JIT has the license 
        as long as we dont have specify it Strictfp

Marking it Strictfp will make the result Uniform on every hardware and platform, because its precised value will be same
One scenario I can see is in a distributed application (or multiplayer game) where all floating-point calculations need to 
be deterministic no matter what the underlying hardware or CPU is.

0

'strictfp' 关键字用于强制浮点计算(float 或 double)在 Java 中符合 IEEE 的 754 标准,显式地。如果您不使用 strictfp 关键字,则浮点精度取决于目标平台的硬件。

如果一个接口或类被声明为 strictfp,则该接口或类中的所有方法和嵌套类型都隐式地是 strictfp。

参考 link


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接