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. У меня есть код, который проходит много двойников, поэтому мне не нужно преобразование из double в bigdecimal. Я понял, что время обработки BigDecimal велико. Итак, я делаю format.format(value, buffer, position) И вижу, что точность теряется. Я получаю результат -1,350,825,904,190,560,000,000,000,000.

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

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


person Anusha Pachunuri    schedule 16.04.2015    source источник
comment
может быть, имеет смысл опубликовать код, который тоже дает неверные результаты?   -  person Iłya Bursov    schedule 17.04.2015
comment
Вы уверены, что это действительно имеет значение? Если бы у вас было Double value = new Double(0.1), хотели бы вы видеть полную точность (то есть 0.1000000000000000055511151231257827021181583404541015625)?   -  person user2357112 supports Monica    schedule 17.04.2015
comment
@ user2357112 ммм, я говорю о больших числах, таких как 1350825904190559999913623552.00 здесь. Так что да, это имеет значение.   -  person Anusha Pachunuri    schedule 17.04.2015
comment
@Lashane, единственное изменение в коде - это не преобразование в bigdecimal. я вставил это в код   -  person Anusha Pachunuri    schedule 17.04.2015
comment
@AnushaPachunuri: Если вы хотите получить от двойника более 15 цифр точности, вам нужно больше, чем двойник может сделать для вас. Я думаю, что либо вам нужен BigDecimal, либо вам не нужно сохранять полную точность.   -  person user2357112 supports Monica    schedule 17.04.2015


Ответы (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)));
person Elliott Frisch    schedule 16.04.2015
comment
Но я думаю, что точность теряется только во время форматирования. Если точность была потеряна при создании двойного числа, почему преобразование в bigdecimal сохраняет его. Таким образом, это не падает в том же случае, поскольку вы не можете получить большую точность, чем двойное число, путем преобразования двойного числа в BigDecimal, не так ли? - person Anusha Pachunuri; 17.04.2015
comment
Вы сказали много дублей. Что такое 0,1+0,2? - person Elliott Frisch; 17.04.2015

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

Double value = new Double(-1350825904190559999913623552.00);

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

person user207421    schedule 16.04.2015

Проблема заключается в форматировании вывода, в частности, в том, как двойные числа преобразуются в строки по умолчанию. Каждое двойное число имеет точное значение, но оно также является результатом преобразования строки в двойное число для диапазона десятичных дробей. В этом случае точное значение двойного числа равно -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
person Patricia Shanahan    schedule 17.04.2015
comment
Мне нравится твое объяснение. хотя программа немного утомительна для меня. Итак, у меня есть два вопроса: 1) что это за диапазон [-1350825904190560137352577024, -1350825904190559862474670080] 2) Судя по тому, что вы говорите, форматирование двойного числа в таком случае показывает только цифры, достаточные для однозначно идентифицировать его из диапазона? даже если двойное число находится в диапазоне 2 ^ 53 и Double.MAX_VALUE , форматирование не будет правильно вмещать все цифры? - person Anusha Pachunuri; 17.04.2015
comment
Извините, я кажется понял! это отличное объяснение. меня заинтриговал тот факт, что строка для двойных преобразований для указанного вами диапазона десятичных дробей одинакова. Есть ли лучшие ссылки, чтобы прочитать об этом? - person Anusha Pachunuri; 17.04.2015
comment
JLS относится к документации API для java.lang.Double valueOf для двойного преобразования строки. В частности, это точное числовое значение затем концептуально преобразуется в бесконечно точное двоичное значение, которое затем округляется до типа double по обычному правилу округления до ближайшего арифметики с плавающей запятой IEEE 754. - person Patricia Shanahan; 18.04.2015

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

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

person Zyn    schedule 16.04.2015
comment
Некоторые, но не все, числа между 2^53 и Double.MAX_VALUE точно представимы, и мне кажется, что это одно из них. - person Patricia Shanahan; 17.04.2015
comment
en.wikipedia.org/wiki/ - person Zyn; 17.04.2015
comment
Я знаком с этой веб-страницей, и на ней нет ничего, что противоречило бы утверждению о том, что некоторые большие числа точно представимы, и это одно из них. Вы можете проверить это, используя преобразователь десятичных чисел в числа с плавающей запятой. - person Patricia Shanahan; 17.04.2015
comment
@Patricia - так вы говорите, что это число может быть представлено как двойное и не теряет своей точности только потому, что оно двойное, но при его форматировании. правильный? - person Anusha Pachunuri; 17.04.2015