A Closer Look at the Java 2.2250738585072012e-308 Bug

Java’s decimal to floating-point conversion routine, the doubleValue() method of its FloatingDecimal class, goes into an infinite loop when converting the decimal string 2.2250738585072012e-308 to double-precision binary floating-point. I took a closer look at the bug, by tracing the doubleValue() method in the Eclipse IDE for Java (thanks to Konstantin Preißer for helping me set that up). What I found was that our initial analysis of the bug was wrong; what actually happens is that doubleValue()’s correction loop oscillates between two values, 0x1p-1022 and 0x0.fffffffffffffp-1022.

The Failing Code in doubleValue()

This section of code in the correction loop of doubleValue() is where the infinite loop manifests itself:

} else {
    // difference is non-trivial.
    // could scale addend by ratio of difference to
    // halfUlp here, if we bothered to compute that difference.
    // Most of the time ( I hope ) it is about 1 anyway.
    dValue += ulp( dValue, overvalue );
    if ( dValue == 0.0 || dValue == Double.POSITIVE_INFINITY )
        break correctionLoop; // oops. Fell off end of range.
    continue; // try again.

(This code is from JDK 6 Update 23; it lives in file j2se/src/share/classes/sun/misc/FloatingDecimal.java.)

To see the value of ulp() and the before and after values of dValue, I inserted printf() statements to view them as hexadecimal floating point constants.

Odd Numbered Passes of the Loop

These are the values of ulp() and dValue for the first, third, fifth, etc. time through the highlighted line of code:

ulp():                      -0x0.0000000000001p-1022
dValue before adding ulp():  0x1.0p-1022
dValue after adding ulp():   0x0.fffffffffffffp-1022

In other words, 2-1074 is subtracted from 2-1022, giving

1.111111111111111111111111111111111111111111111111111 x 2-1023.

Even Numbered Passes of the Loop

These are the values of ulp() and dValue for the second, fourth, sixth, etc. time through the highlighted line of code:

ulp():                       0x0.0000000000001p-1022
dValue before adding ulp():  0x0.fffffffffffffp-1022
dValue after adding ulp():   0x1.0p-1022

In other words, 2-1074 is added to

1.111111111111111111111111111111111111111111111111111 x 2-1023, giving

2-1022. Now the correction loop is right back where it started.

The Initial Estimate Was Correct

The initial value of dValue was correct — it needed no adjustment. Therein lies the problem.

An Old Suggested Fix

In OpenJDK bug report 100119 — an earlier report of what turns out to be the same bug — there is a suggested one line of code fix: change this line of code in the correction loop

if ( (bigIntNBits == 1) && (bigIntExp > -expBias) ){


if ( (bigIntNBits == 1) && (bigIntExp > -expBias+1) ){

I tried it out, as did Konstantin; it stops the infinite loop and returns the correctly rounded result. Before the fix, the ‘if’ statement evaluates to true (and continues to do so on every odd numbered pass of the loop): bigIntExp=-1022 and -expBias=-1023. Adding one to -expBias makes the ‘if’ condition false, which leads to no adjustment — and an exit from the loop.

Is The Fix Correct?

The suggested fix works for this case — but does it work in all cases? We’ll have to wait and see what the Java experts have to say about it.

This is Not the Same as the PHP Bug

PHP’s decimal to floating-point conversion routine went into an infinite loop for a different number, a number just below the normal/subnormal number boundary: 2.2250738585072011e-308. The fix was to add the ‘volatile’ keyword to a variable used in the adjustment of the converted result. This addressed an issue related to how the code ran on the x87 architecture.

PHP’s decimal to floating-point conversion routine is C code — it is a version of David Gay’s strtod() function. Java’s decimal to floating-point conversion routine is java code, although obviously modeled on David Gay’s C code. Apparently, it was changed just enough to avoid the x87 error — but it introduced a new error.

RSS feed icon
RSS e-mail icon

One comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

(Cookies must be enabled to leave a comment...it reduces spam.)