Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

losing precision converting from java BigDecimal to double

I am working with an application that is based entirely on doubles, and am having trouble in one utility method that parses a string into a double. I've found a fix where using BigDecimal for the conversion solves the issue, but raises another problem when I go to convert the BigDecimal back to a double: I'm losing several places of precision. For example:

import java.math.BigDecimal;
import java.text.DecimalFormat;

public class test {
    public static void main(String [] args){
        String num = "299792.457999999984";
        BigDecimal val = new BigDecimal(num);
        System.out.println("big decimal: " + val.toString());
        DecimalFormat nf = new DecimalFormat("#.0000000000");
        System.out.println("double: "+val.doubleValue());
        System.out.println("double formatted: "+nf.format(val.doubleValue()));
    }
}

This produces the following output:

$ java test
big decimal: 299792.457999999984
double: 299792.458
double formatted: 299792.4580000000

The formatted double demonstrates that it's lost the precision after the third place (the application requires those lower places of precision).

How can I get BigDecimal to preserve those additional places of precision?

Thanks!


Update after catching up on this post. Several people mention this is exceeding the precision of the double data type. Unless I'm reading this reference incorrectly: http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.2.3 then the double primitive has a maximum exponential value of Emax = 2K-1-1, and the standard implementation has K=11. So, the max exponent should be 511, no?

like image 552
Edward Q. Bridges Avatar asked Apr 21 '11 20:04

Edward Q. Bridges


People also ask

Which is more accurate BigDecimal or double?

A BigDecimal is an accurate way of expressing numbers. A Double has a reliable accuracy. Going with doubles of various magnitudes (say d1=1000.0 and d2=0.001) could occur in the 0.001 being dropped collectively when summing as the variation in magnitude is so large. With BigDecimal this would not occur.

Can BigDecimal be converted to double?

math. BigDecimal. doubleValue() is an in-built function which converts the BigDecimal object to a double. This function converts the BigDecimal to Double.

How does BigDecimal compare to double in Java?

Java provides the built-in function compareTo() which compares the two BigDecimals . The comparison can not be done using the > , < or = operators as these operators can only be used for the primitive data types like int, long and double.

How do you convert BigDecimal to double without exponential?

So, to obtain a double from a BigDecimal , simply use bd. doubleValue() . There is no need to use an intermediate string representation, and it can even be detrimental to do so, because if the string representation performs some rounding, you don't get the best approximation of the value in the BigDecimal .


2 Answers

You've reached the maximum precision for a double with that number. It can't be done. The value gets rounded up in this case. The conversion from BigDecimal is unrelated and the precision problem is the same either way. See this for example:

System.out.println(Double.parseDouble("299792.4579999984"));
System.out.println(Double.parseDouble("299792.45799999984"));
System.out.println(Double.parseDouble("299792.457999999984"));

Output is:

299792.4579999984
299792.45799999987
299792.458

For these cases double has more than 3 digits of precision after the decimal point. They just happen to be zeros for your number and that's the closest representation you can fit into a double. It's closer for it to round up in this case, so your 9's seem to disappear. If you try this:

System.out.println(Double.parseDouble("299792.457999999924"));

You'll notice that it keeps your 9's because it was closer to round down:

299792.4579999999

If you require that all of the digits in your number be preserved then you'll have to change your code that operates on double. You could use BigDecimal in place of them. If you need performance then you might want to explore BCD as an option, although I'm not aware of any libraries offhand.


In response to your update: the maximum exponent for a double-precision floating-point number is actually 1023. That's not your limiting factor here though. Your number exceeds the precision of the 52 fractional bits that represent the significand, see IEEE 754-1985.

Use this floating-point conversion to see your number in binary. The exponent is 18 since 262144 (2^18) is nearest. If you take the fractional bits and go up or down one in binary, you can see there's not enough precision to represent your number:

299792.457999999900 // 0010010011000100000111010100111111011111001110110101
299792.457999999984 // here's your number that doesn't fit into a double
299792.458000000000 // 0010010011000100000111010100111111011111001110110110
299792.458000000040 // 0010010011000100000111010100111111011111001110110111
like image 172
WhiteFang34 Avatar answered Sep 22 '22 08:09

WhiteFang34


The problem is that a double can hold 15 digits, while a BigDecimal can hold an arbitrary number. When you call toDouble(), it attempts to apply a rounding mode to remove the excess digits. However, since you have a lot of 9's in the output, that means that they keep getting rounded up to 0, with a carry to the next-highest digit.

To keep as much precision as you can, you need to change the BigDecimal's rounding mode so that it truncates:

BigDecimal bd1 = new BigDecimal("12345.1234599999998");
System.out.println(bd1.doubleValue());

BigDecimal bd2 = new BigDecimal("12345.1234599999998", new MathContext(15, RoundingMode.FLOOR));
System.out.println(bd2.doubleValue());
like image 34
Anon Avatar answered Sep 21 '22 08:09

Anon