Math.pow yields different result depending on java version
Asked Answered
P

4

38

I'm running the following code on a JDK Version 1.7.0_60:

System.out.println(Math.pow(1.5476348320352065, (0.3333333333333333)));

The result is: 1.1567055833133086

I'm running exactly the same code on a JDK Version 1.7.0.

The result is: 1.1567055833133089

I understand that double is not infinitely precise, but was there a change in the java spec that causes the difference?

PS: Because we use a legacy system, Big Decimal is not an option.

Edit: I was able to track down the time of the change: It was introduced in the JDK Version 1.7.0_40 (as compared to Version 1.7.0_25).

Profession answered 20/8, 2014 at 11:56 Comment(9)
It may well be a difference in which hardware instructions the JIT uses.Quadratics
What affect does this have on your program's output (other than the difference shown above)? How does this affect the functionality of your program? Is your code written in a way to allow for tolerance of double's limitations.Mullinax
Out of curiosity, in what field is you application? I have hard time imagining a situation where a difference of 3E-16 would make a significant difference.Hereupon
It is used to calculate currencies. The problem is, the number is later used to calculate some relative errors, and that difference sums up to a significant difference in the end result (>0.1). Unfortunately, a change from double to big decimal would be hugely expensive.Profession
Would a Java long not be sufficient? That gives you 18 orders of magnitude to play with, and will be much cheaper than BigDecimal.Ewart
Were the two calculations on the same machine? as according to the std, a faithful rounding, i.e. correct within 1 ulp, is required as the power function is very expensive to be correctly rounded. Therefore pow function is not portable.Retral
Yes, the two calculations were on the same machine. We tried the same test on another machine, and the behavior is the same.Profession
If these small relative errors in the elementary computations sum up to something that big in the result, then there are other problems in the code. The implied condition number is outside the solar system. Does the problem actually have a well-defined result? What is the margin of the relative error expected from the choice of numerical algorithm? Check that the algorithms used matches the implementation, try to transform formulas to avoid cancellations, use the relatively cheap error compensated summation methods found in Knuth, Higham, Rhump.Hasp
You can use the MPFR library if you seek portable correctly rounded results for the complicated math functions.Retral
P
5

To produce consistent results between all Java versions, the solution was to use StrictMath.pow() instead of Math.pow().

For background information on what might cause the difference, refer to this answer.

Profession answered 1/9, 2014 at 8:6 Comment(0)
E
41

but was there a change in the java spec that causes the difference?

No.* According to the Javadocs for Math.pow, a difference of up to one ULP (Unit in the Last Place) is permitted. If we take a look at your two values:

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

we get:

3ff281ddb6b6e675
3ff281ddb6b6e676

which indeed differ by one ULP.

What you're seeing is probably due to slight differences in the sequence of floating-point instructions used by the JDK/JVM to implement these operations.


* At least, not so far as I know!
Ewart answered 20/8, 2014 at 12:12 Comment(2)
@biziclop: For that, we'd need to trawl through the source code for the platform-specific native methods, which I don't fancy doing right now ;)Ewart
For the record, the exact result is 1.156705583313308737..., and the answer given by Java 1.7.0_60 is more accurate.Gregory
N
8

There was no change in the spec, but there have been some changes in the hotspot optimizer that might (!) be related to this.

I dug up these code parts:

(these are not exactly the versions where these changes have been introduced, I just picked them because of the version information that you provided).

The changes (and what the code is doing at all) are far beyond what I can analyze in reasonable time, but maybe someone finds this reference interesting or useful.

Nalepka answered 21/8, 2014 at 10:14 Comment(0)
S
5

If you want repeatable floating point values between JVMs you can use the strictfp keyword, see following question When should I use the "strictfp" keyword in java?

Salvia answered 20/8, 2014 at 12:31 Comment(6)
If I annotate the calling method/class with strictfp, the difference between version 1.7.0_25 and 1.7.0_40 remains the same.Profession
@Profession strictfp doesn't do anything with respect to the methods in Math. Try StrictMath.pow() and see if it produces consistent results.Wad
StrictMath.pow() appears to specify the use of fdlibm. You should get consistent results, but they aren't especially high-quality.Acklin
@Acklin That bring us to the almost-philosophical question of why IEEE 754 did not, for those functions that couldn't be computed to 0.5ULP cheaply enough to mandate it, standardize some specific algorithms with the accuracy/code size/speed trade-offs that were the state of the art at the time of standardizing. And the answer to that appears to be that they did not want to make life harder than necessary for newer, more accurate algorithms, which would have had to fight against the standard. In retrospect, well done IEEE 754 (and Sun for relegating fdlibm to StrictMath).Juli
@PascalCuoq: I believe (from my dim memories of someone's (Kahan's) history of the IEEE 754 standard) they thought about this and rejected the idea so they wouldn't crimp the style of future implementations that could possibly be faster or more accurate. "Faster" also being an acceptable goal.Acklin
Thanks, StrictMath indeed produces consistent results. The result is now 1.1567055833133086, regardless of the JDK version I use.Profession
P
5

To produce consistent results between all Java versions, the solution was to use StrictMath.pow() instead of Math.pow().

For background information on what might cause the difference, refer to this answer.

Profession answered 1/9, 2014 at 8:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.