37

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).

jmiserez
  • 2,991
  • 1
  • 23
  • 34
Damnum
  • 1,839
  • 16
  • 35
  • 5
    It may well be a difference in which hardware instructions the JIT uses. – Jon Skeet Aug 20 '14 at 11:58
  • 3
    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. – Hovercraft Full Of Eels Aug 20 '14 at 11:59
  • 2
    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. – Sergey Kalinichenko Aug 20 '14 at 12:02
  • 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. – Damnum Aug 20 '14 at 12:06
  • Would a Java `long` not be sufficient? That gives you 18 orders of magnitude to play with, and will be much cheaper than BigDecimal. – Oliver Charlesworth Aug 20 '14 at 12:27
  • 2
    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. – Garp Aug 20 '14 at 21:23
  • Yes, the two calculations were on the same machine. We tried the same test on another machine, and the behavior is the same. – Damnum Aug 21 '14 at 11:24
  • 2
    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. – Lutz Lehmann Aug 21 '14 at 11:25
  • You can use the MPFR library if you seek portable correctly rounded results for the complicated math functions. – Garp Aug 21 '14 at 12:22

4 Answers4

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!
Lambart
  • 1,985
  • 2
  • 21
  • 37
Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • 5
    @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 ;) – Oliver Charlesworth Aug 20 '14 at 12:25
  • 13
    For the record, the exact result is `1.156705583313308737...`, and the answer given by Java 1.7.0_60 is more accurate. – Tavian Barnes Aug 20 '14 at 14:50
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.

Marco13
  • 53,703
  • 9
  • 80
  • 159
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?

Community
  • 1
  • 1
user3679868
  • 693
  • 4
  • 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. – Damnum Aug 20 '14 at 16:38
  • 8
    @Damnum `strictfp` doesn't do anything with respect to the methods in `Math`. Try [`StrictMath.pow()`](http://docs.oracle.com/javase/7/docs/api/java/lang/StrictMath.html#pow(double,%20double)) and see if it produces consistent results. – ntoskrnl Aug 20 '14 at 19:17
  • `StrictMath.pow()` appears to specify the use of `fdlibm`. You should get consistent results, but they aren't especially high-quality. – tmyklebu Aug 20 '14 at 22:53
  • 2
    @tmyklebu 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`). – Pascal Cuoq Aug 21 '14 at 10:08
  • @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. – tmyklebu Aug 21 '14 at 10:40
  • Thanks, StrictMath indeed produces consistent results. The result is now 1.1567055833133086, regardless of the JDK version I use. – Damnum Aug 21 '14 at 11:19
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.

Community
  • 1
  • 1
Damnum
  • 1,839
  • 16
  • 35