Anusha Pachunuri
Anusha Pachunuri

Reputation: 1419

Java DecimalFormat losing precision while formatting double

When i execute the below code:

public class Test {
    public static void main(String args[]){
        DecimalFormat format = new DecimalFormat();
        Double value = new Double(-1350825904190559999913623552.00);

        StringBuffer buffer = new StringBuffer();
        FieldPosition position = new FieldPosition(0);
        format.format(new BigDecimal(value), buffer, position);
        System.out.println(buffer);
    }
}

This correctly prints -1,350,825,904,190,559,999,913,623,552. I have code which does go through a lot of doubles so I dont want the conversion from double to bigdecimal. I figured the processing time for BigDecimal is large. So i do format.format(value, buffer, position) And i see the precision is lost. The output I get is -1,350,825,904,190,560,000,000,000,000.

What am i doing wrong here? Is there a better way to deal with this and still retain the precision. I don't want to deal with BigDecimals here but just work with the decimals.

Any suggestions?

Upvotes: 4

Views: 1388

Answers (4)

Patricia Shanahan
Patricia Shanahan

Reputation: 26185

The issue is in the output formatting, specifically how doubles are converted to strings by default. Each double number has an exact value, but it is also the result of string to double conversion for a range of decimal fractions. In this case, the exact value of the double is -1350825904190559999913623552, but the range is [-1350825904190560137352577024,-1350825904190559862474670080].

The Double toString conversion picks the number from that range with the fewest significant digits, -1.35082590419056E27. That string does convert back to the original value.

If you really want to see the exact value, not just enough digits to uniquely identify the double, your current BigDecimal approach works well.

Here is the program I used to calculate the numbers in this answer:

import java.math.BigDecimal;

public class Test {
  public static void main(String args[]) {
    double value = -1350825904190559999913623552.00;
    /* Get an exact printout of the double by conversion to BigDecimal
     * followed by BigDecimal output. Both those operations are exact.
     */
    BigDecimal bdValue = new BigDecimal(value);
    System.out.println("Exact value: " + bdValue);
    /* Determine whether the range is open or closed. The half way
     * points round to even, so they are included in the range for a number
     * with an even significand, but not for one with an odd significand.
     */
    boolean isEven = (Double.doubleToLongBits(value) & 1) == 0;
    /* Find the lower bound of the range, by taking the mean, in
     * BigDecimal arithmetic for exactness, of the value and the next
     * exactly representable value in the negative infinity direction.
     */
    BigDecimal nextDown = new BigDecimal(Math.nextAfter(value,
        Double.NEGATIVE_INFINITY));
    BigDecimal lowerBound = bdValue.add(nextDown).divide(BigDecimal.valueOf(2));
    /* Similarly, find the upper bound of the range by going in the
     * positive infinity direction.
     */
    BigDecimal nextUp = new BigDecimal(Math.nextAfter(value,
        Double.POSITIVE_INFINITY));
    BigDecimal upperBound = bdValue.add(nextUp).divide(BigDecimal.valueOf(2));
    /* Output the range, with [] if closed, () if open.*/
    System.out.println("Range: " + (isEven ? "[" : "(") + lowerBound + ","
        + upperBound + (isEven ? "]" : ")"));
    /* Output the result of applying Double's toString to the value.*/
    String valueString = Double.toString(value);
    System.out.println("toString result: " + valueString);
    /* And use BigDecimal as above to print the exact value of the result
     * of converting the toString result back again.
     */
    System.out.println("exact value of toString result as double: "
        + new BigDecimal(Double.parseDouble(valueString)));
  }
}

Output:

Exact value: -1350825904190559999913623552
Range: [-1350825904190560137352577024,-1350825904190559862474670080]
toString result: -1.35082590419056E27
exact value of toString result as double: -1350825904190559999913623552

Upvotes: 2

user207421
user207421

Reputation: 311054

It isn't lost during formatting. It is lost right here:

Double value = new Double(-1350825904190559999913623552.00);

A double only has about 15.9 significant decimal digits. It doesn't fit. There was a precision loss at compile time when the floating-point literal was converted.

Upvotes: 1

Zyn
Zyn

Reputation: 614

You cannot represent 1350825904190559999913623552.00 accurately with a Double. If you would like to know why, explore this article.

Should you want to represent the value, I would advise using the code you have used in your question: new BigDecimal( value ), where value is actually a String representation.

Upvotes: 0

Elliott Frisch
Elliott Frisch

Reputation: 201537

double doesn't have infinite precision, and you can't gain more precision than a double has by converting a double to a BigDecimal (like you can't gain more precision with an int when you do double r = 1/3; which is 0.0 because it widens an int to a double). Instead, you could use a String. Something like

DecimalFormat format = new DecimalFormat();
String value = "-1350825904190559999913623552.00";
System.out.println(format.format(new BigDecimal(value)));

Upvotes: 2

Related Questions