Oti's Jython Blog

Wednesday, February 9, 2011

No more Java Double Trouble

Lately there has been quite of rumour in the Java community about the endless loop caused by a certain range of values. Any java application parsing floating point values was vulnerable, especially dynamic languages.

Now the good news is that Oracle has fixed the issue!


Disclaimer

Only read on if your application needs to run on older, unpatched Java versions.


Analysis

Charles Oliver Nutter made the first trial of a workaround. The hot points in Java are:
  • new Double(String)
  • Double.valueOf(String)
  • Double.parseDouble(String)
  • new Float(String)
  • Float.parseFloat(String)
  • Float.valueOf(String)
  • BigDecimal.doubleValue()
  • BigDecimal.floatValue()
These calls are only blocking for a small interval [2.2250738585072011e-308, 2.2250738585072013e-308] of String input values. In fact the interval is even smaller.


Solution

There is a very pragmatic workaround to this problem:
On parsing, use a quite fast String analysis to find out if the input is suspicious. A suspicious value contains the digit sequence "22250738585072". In this case, use the slower BigDecimal parsing to determine if the value really is inside the dangerous interval. Dangerous values are rounded to the most nearby border of the interval, which is a safe value. For neither suspicious nor dangerous values, the standard Java floating decimal parsing is used.
The code looks like:
final protected static Double decimalValueOf(String s) {
Double result = null;
if (s != null) {
if (isSuspicious(s)) {
// take the slow path
result = parseSafely(s);
} else {
result = Double.valueOf(s);
}
}
return result;
}

final private static Double parseSafely(String s) {
Double result;
BigDecimal bd = new BigDecimal(s);
if (bd.compareTo(UPPER) <> 0) {
if (bd.compareTo(MIDDLE) >= 0) {
result = UPPER_DOUBLE;
} else {
result = LOWER_DOUBLE;
}
} else {
result = Double.valueOf(s);
}
return result;
}
For the BigDecimal.doubleValue() case, we need to make sure that we do not feed dangerous values into the standard Java floating decimal parsing:
final protected static double decimalValue(BigDecimal bigDecimal) {
double result = 0.0;
if (bigDecimal != null) {
if (bigDecimal.compareTo(UPPER) <> 0) {
result = decimalValueOf(bigDecimal.toString()).doubleValue();
} else {
result = bigDecimal.doubleValue();
}
}
return result;
}
What you see here is the heart of the SafeDecimalParser class. For real usage, there are two convenience classes: SafeDoubleParser and SafeFloatParser. The following table lists the original calls, and how you would replace them:

String s; BigDecimal bd;
new Double(s)
-> SafeDoubleParser.valueOf(s)
Double.parseDouble(s)
-> SafeDoubleParser.parseDouble(s)
Double.valueOf(s)
-> SafeDoubleParser.valueOf(s)
new Float(s)
-> SafeFloatParser.valueOf(s)
Float.parseFloat(s)
-> SafeFloatParser.parseFloat(s)
Float.valueOf(s)
-> SafeFloatParser.valueOf(s)
bd.doubleValue()
-> SafeDoubleParser.doubleValue(bd)
bd.floatValue()
-> SafeFloatParser.floatValue(bd)

The DoubleTroubleTestSuite.jar contains all the sources for the safe parsers. Running java -jar DoubleTroubleTestSuite.jar executes all the junit tests on your favourite platform. I successfully tested it on:
  • Mac OS X 10.5.8
  • Windows 7
  • Ubuntu 10.4
  • Ubuntu 10.10
  • Open Solaris

Downsides
  • The small performance impact for the detection of suspicious values
  • Some values are not exact, however the maximum error is less than 6.2e-325

Nevertheless, this hopefully might be useful.

You find the code inside the .jar file, besides the .class files in the org.python.util package.
It is under the Python Software Foundation License, which means you can freely use and redistribute it.
Happy coding!