Math.pow() in Java 8 and Java 13 returns different result

Viewed 460

This following code returns different results on Java 8 and 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: (The same result as Python, Rust)

val = 1.000003058823805100000000

The questions are these:

  • Any documentation to describe this kind of behavior?
  • How to implement Java 8's Math.pow() in Python?
  • How to ensure strict consistency with older programs using the Java 8 library?
3 Answers

If we just use Double.toString() to print the answers then the two different results would be

1.0000030588238054
1.0000030588238051

The extra digits at the end are just a factor of the String.format(). We see the numbers only differ by the last significant decimal digit. Converting the two numbers to hexadecimal give

1.000033518c576
1.000033518c575

so the binary representations only differ by one unit in the last place (ulp).

Reading the spec for Math.pow we find

"The computed result must be within 1 ulp of the exact result."

The true value is close to 1.000003058823805246468 (WolframAlpha) somewhere between the two answers, so both are within the spec.

All which has happened is that the library has had a slight change in algorithm, maybe to make it faster.

Note that

System.out.println("val = " + String.format("%.24f", ret));

is an unnecessary mix from printf style formatting, string concatenation, and println. You can use printf in the first place:

System.out.printf("val = %.24f%n", ret);

However, there is no point in requesting 24 decimal digits, when the double precision does not even remotely provide that many digits. When you use

System.out.println("val = " + ret);

instead, it will default to the actually available digits, which yields

val = 1.0000030588238054

for Java 8 and

val = 1.0000030588238051

for Java 11.

So the difference is only in the last digit. Or more precisely

double d1 = 1.0000030588238051, d2 = 1.0000030588238054;
System.out.println((d2 - d1) == Math.ulp(d1));

prints true, so the distance between these two values is the smallest possible with double. There is no other double value in between them. The specification of pow says:

The computed result must be within 1 ulp of the exact result.

Since the code above showed, that both results have a distance of one ulp, both results would be correct when the exact result lies between both results. Wolfram Alpha says, the exact result starts with

1.000003058823805246…

So it lies between these two results. So both results are correct according to the specification.

For easier comparison:

1.0000030588238054         JDK  8
1.000003058823805246…      Wolfram Alpha
1.0000030588238051         JDK 11

After some investigation, the quick answer is strictfp. ref: https://en.wikipedia.org/wiki/Strictfp

In java8, the default behavior is using x87 FPU on x86 machines, which will use extended double(80bit) as intermediate values. So the result will not be guaranteed to be the same value on another platform.

In java11 or java13, the default behavior is using IEEE double(64bit) as intermediate values.

Since almost all modern languages guarantee stable results via IEEE-floats, this behavior is tricky to mimic in other languages or new java. Inline x87 FPU assembly might help.

Related