Math.pow在不同版本的Java中产生不同的结果

38

我正在JDK版本1.7.0_60上运行以下代码:

System.out.println(Math.pow(1.5476348320352065, (0.3333333333333333)));
结果是: 1.1567055833133086
我正在运行完全相同的代码,使用JDK版本1.7.0。
结果是:1.1567055833133089。
我知道double不是无限精确的,但是Java规范中是否有导致这种差异的更改?
备注:由于我们使用遗留系统,Big Decimal不是一个选项。
编辑:我能够追踪到更改的时间:它是在JDK版本1.7.0_40中引入的(与版本1.7.0_25相比)。

5
可能是JIT使用的硬件指令不同造成的差异。 - Jon Skeet
3
除了上述的区别之外,这会对你的程序输出产生什么影响?这会如何影响你的程序功能?你的代码是否编写成允许双精度浮点数限制的容差? - Hovercraft Full Of Eels
2
出于好奇,您的应用程序属于哪个领域?我很难想象一个3E-16的差异会产生显着的影响的情况。 - Sergey Kalinichenko
2
这两个计算是在同一台机器上进行的吗?根据标准,需要进行忠实舍入,即正确到1 ulp,因为幂函数非常昂贵,需要正确舍入。因此,pow函数不具备可移植性。 - Garp
2
如果在基本计算中出现的这些小相对误差累加到结果中变得如此之大,那么代码中就存在其他问题。隐含的条件数超出了太阳系的范围。问题实际上是否有明确定义的结果?从数值算法的选择中预期的相对误差边际是多少?检查所使用的算法是否与实现匹配,尝试转换公式以避免取消,使用Knuth、Higham、Rhump中发现的相对便宜的误差补偿求和方法。 - Lutz Lehmann
显示剩余4条评论
4个回答

41

但是Java规范是否发生了变化导致了这种差异?

没有。*根据Math.pow的Javadocs,允许最多一个ULP(最后一位单位)的差异。如果我们看一下您的两个值:

System.out.printf("%016x\n", Double.doubleToLongBits(1.1567055833133086));
System.out.printf("%016x\n", Double.doubleToLongBits(1.1567055833133089));

我们得到:

3ff281ddb6b6e675
3ff281ddb6b6e676

事实上它们只相差一个ULP。

你看到的可能是由于JDK/JVM用于执行这些操作的浮点指令序列略有差异导致的。


* 至少,就我所知道的而言!


5
为此,我们需要搜索特定于平台的本地方法的源代码,但我目前不想这样做 ;) - Oliver Charlesworth
14
记录一下,精确结果为 1.156705583313308737...,而 Java 1.7.0_60 给出的答案更准确。 - Tavian Barnes

8

规格没有变化,但是热点优化器发生了一些更改,这可能与此有关。

我挖掘了以下代码部分:

(这些并不是引入这些更改的确切版本,只是因为您提供的版本信息而选取的)。

这些更改(以及代码实际在做什么)远远超出了我可以合理分析的时间范围,但也许有人会觉得这个参考有趣或有用。


5
为了在所有Java版本之间产生一致的结果,解决方案是使用StrictMath.pow()而不是Math.pow()
若想了解导致差异的背景信息,请参考该答案

5

如果我使用strictfp注解调用方法/类,版本1.7.0_25和1.7.0_40之间的差异保持不变。 - Damnum
8
strictfp 关键字在 Math 类的方法中没有任何作用。尝试使用 StrictMath.pow() 方法,并查看是否会产生一致的结果。 - ntoskrnl
StrictMath.pow()似乎指定了使用fdlibm。您应该获得一致的结果,但它们并不特别高质量。 - tmyklebu
2
@tmyklebu 这让我们陷入了一个几乎哲学性的问题,即为什么IEEE 754没有为那些无法以足够便宜的0.5ULP计算的函数标准化一些具有精度/代码大小/速度权衡的特定算法,这些算法是在标准化时最先进的。答案似乎是他们不想让新的、更准确的算法的生活比必要的更艰难,这些算法将不得不与标准进行斗争。回顾过去,IEEE 754做得很好(以及Sun将fdlibm降级为“StrictMath”)。 - Pascal Cuoq
@PascalCuoq:我相信(从我对某人(Kahan)关于IEEE 754标准历史的模糊记忆中),他们考虑过这个问题并拒绝了这个想法,以免束缚未来可能更快或更精确的实现风格。 “更快”也是一个可接受的目标。 - tmyklebu
谢谢,StrictMath确实能够产生一致的结果。无论我使用哪个JDK版本,结果都是1.1567055833133086。 - Damnum

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