Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parsing double values using Double vs BigDecimal in Java

I'm an experienced developer but not a math expert. I know enough about the IEEE floating point specification to be afraid of making assumptions about parsing, printing, and comparing them.

I know I can parse a double from a String using Double.parseDouble(String s). I know I can also parse that same string into a BigDecimal using new BigDecimal(String s), and then ask the BigDecimal for a double using BigDecimal.doubleValue().

I glanced at the API and code for both techniques, and it seems that BigDecimal has a lot of different parsing and conversion options.

Are both techniques (Double.parseDouble(s) and new BigDecimal(s).doubleValue()) guaranteed, for all string inputs, to produce exactly the same double primitive value, provided the value is not outside the range of plus or minus Double.MAX_DOUBLE?

like image 637
Garret Wilson Avatar asked Sep 26 '19 18:09

Garret Wilson


1 Answers

For most input values, both techniques should yield the same values. While it's still possible they might not, it doesn't seem likely.

The BigDecimal(String) constructor Javadocs states:

API Note:

For values other than float and double NaN and ±Infinity, this constructor is compatible with the values returned by Float.toString(float) and Double.toString(double).

However, the Double.parseDouble(String) method states:

Returns a new double initialized to the value represented by the specified String, as performed by the valueOf method of class Double.

And that goes on to describe the format accepted by the method.

Let's Test It!

Let's test some values. It looks like an incredibly huge effort to test this exhaustively, but let's test including some string values that represent values known to produce floating-point errors or to be inexact representations.

public static void main(String[] args)
{
    String[] values = {"0", "0.1", "0.33333333333333333333", "-0", "-3.14159265", "10.1e100",
            "0.00000000000000000000000000000000000000000000000000142857142857",
            "10000000000.000000000000000001", "2.718281828459",
            "-1.23456789e-123", "9.87654321e+71", "66666666.66666667",
            "1.7976931348623157E308", "2.2250738585072014E-308", "4.9E-324",
            "3.4028234663852886E38", "1.1754943508222875E-38", "1.401298464324817E-45",
            String.valueOf(Math.E), String.valueOf(Math.PI), String.valueOf(Math.sqrt(2))
    };
    for (String value : values) {
        System.out.println(isDoubleEqual(value));
    }
}
// Test if the representations yield the same exact double value.
public static boolean isDoubleEqual(String s) {
    double d1 = Double.parseDouble(s);
    double d2 = new BigDecimal(s).doubleValue();
    return d1 == d2;
}

For these values, I get all trues. This is not by any means exhaustive, so it would be very difficult to prove it true for all possible double values. All it would take is one false to show a counterexample. However, this seems to be some evidence that it is true for all legal double string representations.

I also tried leading spaces, e.g. " 4". The BigDecimal(String) constructor threw a NumberFormatException but Double.parseDouble trimmed the input correctly.

The BigDecimal(String) constructor won't accept Infinity or NaN, but you only asked about the normal finite range. The Double.parseDouble method accepts hexadecimal floating point representations but BigDecimal(String) does not.

If you include these edge cases, one method may throw an exception where the other would not. If you're looking for normal base-10 strings of finite values within range, the answer is "it seems likely".

like image 137
rgettman Avatar answered Oct 07 '22 16:10

rgettman