Math.pow() in Java 8 和 Java 13 returns 不同的结果
Math.pow() in Java 8 and Java 13 returns different result
以下代码 returns 在 Java 8 和 Java 11 上的不同结果。
class Playground {
public static void main(String[ ] args) {
long x = 29218;
long q = 4761432;
double ret = Math.pow(1.0 + (double) x / q, 0.0005);
System.out.println("val = " + String.format("%.24f", ret));
}
}
Java 8:
val = 1.000003058823805400000000
Java 11:(与Python,Rust 的结果相同)
val = 1.000003058823805100000000
问题是这些:
- 是否有描述此类行为的文档?
- 如何在 Python 中实现 Java 8 的
Math.pow()
?
- 如何确保与使用 Java 8 库的旧程序的严格一致性?
如果我们只使用 Double.toString() 来打印答案,那么两个不同的结果将是
1.0000030588238054
1.0000030588238051
末尾的额外数字只是 String.format() 的一个因素。我们看到数字仅在最后一个有效的小数位上有所不同。将这两个数字转换为十六进制给出
1.000033518c576
1.000033518c575
所以二进制表示仅在最后一位 (ulp) 相差一个单位。
阅读 Math.pow 的规范,我们发现
“计算结果必须在精确结果的 1 ulp 以内。”
真实值接近 1.000003058823805246468 (WolframAlpha),介于两个答案之间,因此都在规格范围内。
所有发生的事情是图书馆在算法上有轻微的变化,也许是为了让它更快。
注意
System.out.println("val = " + String.format("%.24f", ret));
是 printf
样式格式、字符串连接和 println
的不必要组合。可以先用printf
:
System.out.printf("val = %.24f%n", ret);
但是,当 double
精度甚至无法远程提供那么多数字时,请求 24 位十进制数字是没有意义的。当你使用
System.out.println("val = " + ret);
相反,它将默认为实际可用的数字,这会产生
val = 1.0000030588238054
对于 Java 8 和
val = 1.0000030588238051
对于 Java 11。
所以区别只在最后一位。或者更准确地说
double d1 = 1.0000030588238051, d2 = 1.0000030588238054;
System.out.println((d2 - d1) == Math.ulp(d1));
打印 true
,因此这两个值之间的距离是 double
可能的最小距离。它们之间没有其他 double
值。 specification of pow
says:
The computed result must be within 1 ulp of the exact result.
由于上面的代码表明,两个结果的距离均为 1 ulp,因此当确切结果位于两个结果之间时,两个结果都是正确的。 Wolfram Alpha 表示,确切的结果以
开头
1.000003058823805246…
所以它介于这两个结果之间。因此,根据规范,这两个结果都是正确的。
为了便于比较:
1.0000030588238054 JDK 8
1.000003058823805246… Wolfram Alpha
1.0000030588238051 JDK 11
经过一些调查,快速的答案是 strictfp
。参考:https://en.wikipedia.org/wiki/Strictfp
在 java8 中,默认行为是在 x86 机器上使用 x87 FPU,这将使用扩展双精度(80 位)作为中间值。所以结果将不能保证在另一个平台上是相同的值。
在 java11 或 java13 中,默认行为是使用 IEEE 双精度(64 位)作为中间值。
由于几乎所有现代语言都通过 IEEE-floats 保证稳定的结果,这种行为很难在其他语言或新的 java 中模仿。内联 x87 FPU 汇编可能会有所帮助。
以下代码 returns 在 Java 8 和 Java 11 上的不同结果。
class Playground {
public static void main(String[ ] args) {
long x = 29218;
long q = 4761432;
double ret = Math.pow(1.0 + (double) x / q, 0.0005);
System.out.println("val = " + String.format("%.24f", ret));
}
}
Java 8:
val = 1.000003058823805400000000
Java 11:(与Python,Rust 的结果相同)
val = 1.000003058823805100000000
问题是这些:
- 是否有描述此类行为的文档?
- 如何在 Python 中实现 Java 8 的
Math.pow()
? - 如何确保与使用 Java 8 库的旧程序的严格一致性?
如果我们只使用 Double.toString() 来打印答案,那么两个不同的结果将是
1.0000030588238054
1.0000030588238051
末尾的额外数字只是 String.format() 的一个因素。我们看到数字仅在最后一个有效的小数位上有所不同。将这两个数字转换为十六进制给出
1.000033518c576
1.000033518c575
所以二进制表示仅在最后一位 (ulp) 相差一个单位。
阅读 Math.pow 的规范,我们发现
“计算结果必须在精确结果的 1 ulp 以内。”
真实值接近 1.000003058823805246468 (WolframAlpha),介于两个答案之间,因此都在规格范围内。
所有发生的事情是图书馆在算法上有轻微的变化,也许是为了让它更快。
注意
System.out.println("val = " + String.format("%.24f", ret));
是 printf
样式格式、字符串连接和 println
的不必要组合。可以先用printf
:
System.out.printf("val = %.24f%n", ret);
但是,当 double
精度甚至无法远程提供那么多数字时,请求 24 位十进制数字是没有意义的。当你使用
System.out.println("val = " + ret);
相反,它将默认为实际可用的数字,这会产生
val = 1.0000030588238054
对于 Java 8 和
val = 1.0000030588238051
对于 Java 11。
所以区别只在最后一位。或者更准确地说
double d1 = 1.0000030588238051, d2 = 1.0000030588238054;
System.out.println((d2 - d1) == Math.ulp(d1));
打印 true
,因此这两个值之间的距离是 double
可能的最小距离。它们之间没有其他 double
值。 specification of pow
says:
The computed result must be within 1 ulp of the exact result.
由于上面的代码表明,两个结果的距离均为 1 ulp,因此当确切结果位于两个结果之间时,两个结果都是正确的。 Wolfram Alpha 表示,确切的结果以
开头1.000003058823805246…
所以它介于这两个结果之间。因此,根据规范,这两个结果都是正确的。
为了便于比较:
1.0000030588238054 JDK 8
1.000003058823805246… Wolfram Alpha
1.0000030588238051 JDK 11
经过一些调查,快速的答案是 strictfp
。参考:https://en.wikipedia.org/wiki/Strictfp
在 java8 中,默认行为是在 x86 机器上使用 x87 FPU,这将使用扩展双精度(80 位)作为中间值。所以结果将不能保证在另一个平台上是相同的值。
在 java11 或 java13 中,默认行为是使用 IEEE 双精度(64 位)作为中间值。
由于几乎所有现代语言都通过 IEEE-floats 保证稳定的结果,这种行为很难在其他语言或新的 java 中模仿。内联 x87 FPU 汇编可能会有所帮助。