Java DecimalFormat теряет точность при двойном форматировании

Когда я выполняю код ниже:

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);
    }
}

Это правильно печатает -1,350,825,904,190,559,999,913,623,552, У меня есть код, который проходит через много двойных, поэтому я не хочу, чтобы преобразование из двойного в большое десятичное. Я полагал, что время обработки для BigDecimal велико. Так что я делаю format.format(value, buffer, position)И я вижу, что точность потеряна. Я получаю вывод -1,350,825,904,190,560,000,000,000,000,

Что я здесь не так делаю? Есть ли лучший способ справиться с этим и при этом сохранить точность. Я не хочу иметь дело с BigDecimals здесь, но просто работать с десятичными.

Какие-либо предложения?

4 ответа

double не имеет бесконечной точности, и вы не можете получить больше точности, чем double имеет преобразование double к BigDecimal (как вы не можете получить больше точности с int когда вы делаете double r = 1/3; который 0.0 потому что это расширяет int к double). Вместо этого вы можете использовать String, Что-то вроде

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

Проблема заключается в форматировании выходных данных, а именно в том, как двойники по умолчанию преобразуются в строки. Каждое двойное число имеет точное значение, но оно также является результатом преобразования строки в двойное для диапазона десятичных дробей. В этом случае точное значение двойного числа равно -1350825904190559999913623552, но диапазон равен [-1350825904190560137352577024,-1350825904190559862474670080].

Преобразование Double toString выбирает число из этого диапазона с наименьшим количеством значащих цифр -1,35082590419056E27. Эта строка преобразует обратно в исходное значение.

Если вы действительно хотите увидеть точное значение, а не просто количество цифр, чтобы однозначно идентифицировать двойное число, ваш текущий подход BigDecimal работает хорошо.

Вот программа, которую я использовал для вычисления чисел в этом ответе:

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)));
  }
}

Выход:

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

Это не теряется во время форматирования. Это потеряно прямо здесь:

Double value = new Double(-1350825904190559999913623552.00);

double только около 15,9 значащих десятичных цифр. Это не подходит. Произошла потеря точности во время компиляции, когда был преобразован литерал с плавающей точкой.

Вы не можете точно представлять 1350825904190559999913623552.00 с Double, Если вы хотите знать почему, изучите эту статью.

Если вы хотите представить значение, я бы посоветовал использовать код, который вы использовали в своем вопросе: new BigDecimal( value ), где value на самом деле String представление.

Другие вопросы по тегам