“новый BigDecimal (13.3D)” приводит к неточному “13.3000000000000007105..”?

Как получается, что Java BigDecimal это может быть болезненным?

Double d = 13.3D;

BigDecimal bd1 = new BigDecimal(d);
BigDecimal bd2 = new BigDecimal(String.valueOf(d));


System.out.println("RESULT 1: "+bd1.toString());
System.out.println("RESULT 2: "+bd2.toString());

RESULT 1: 13.300000000000000710542735760100185871124267578125
RESULT 2: 13.3

Есть ли какая-либо ситуация, где Результат 1 был бы желаем? Я знаю что Java 1.5, измененный toString() метод, но действительно ли это было намеченным последствием?

Также я понимаю это BigDecimal имеет doubleValue() и т.д., но библиотека, что я работаю с услужливо использованием a toString() и я не могу изменить это :-(

Удачи.

16
задан Damo 15 May 2012 в 15:19
поделиться

5 ответов

Ну, API действительно обращается к этому очевидному несоответствию в конструкторе BigDecimal(double val):

  1. результаты этого конструктора могут быть несколько непредсказуемыми. Можно было бы предположить, что запись нового BigDecimal (0.1) в Java создает BigDecimal, который точно равен 0,1 (немасштабированное значение 1, с масштабом 1), но это на самом деле равно 0,1000000000000000055511151231257827021181583404541015625. Это вызвано тем, что 0.1 не может быть представлен точно как двойное (или, в этом отношении, как двоичная дробь никакой конечной длины). Таким образом значение, которое передается в конструктору, не точно равно 0,1, появления, несмотря на это.

  2. Строковый конструктор, с другой стороны, совершенно предсказуем: запись нового BigDecimal ("0.1") создает BigDecimal, который точно равен 0,1, как можно было бы ожидать. Поэтому обычно рекомендуется, чтобы конструктор String использовался в предпочтении к этому.

  3. , Когда двойное должно использоваться в качестве источника для BigDecimal, обратите внимание, что этот конструктор обеспечивает точное преобразование; это не дает тот же результат как преобразование вдвое большего по сравнению со Строкой с помощью Double.toString (двойной) метод и затем с помощью BigDecimal (Строка) конструктор. Для получения того результата, используют статический valueOf (двойной) метод .

Мораль истории: боль кажется нанесенной самому себе, просто используйте new BigDecimal(String val) или BigDecimal.valueOf(double val) вместо этого =)

50
ответ дан 30 November 2019 в 15:06
поделиться

Ваша проблема не имеет никакого отношения BigDecimal, и все с Double, который не может представить 13.3 точно, так как она использует двоичные дроби внутренне.

, Таким образом, Ваша ошибка представлена в самой первой строке. Первое BigDecimal просто консервы это, в то время как String.valueOf() делает некоторое подозрительное округление, которое заставляет второй иметь желаемое содержание, в значительной степени через удачу.

11
ответ дан 30 November 2019 в 15:06
поделиться

Вы могли бы хотеть сообщить себе о том, как значения с плавающей точкой реализованы ( IEEE 754-1985 ). И внезапно, все станет совершенно ясным.

8
ответ дан 30 November 2019 в 15:06
поделиться

Это не отказ BigDecimal - это - отказ double. BigDecimal точно представляет точный значение d. String.valueOf только показывает результат нескольким десятичным разрядам.

5
ответ дан 30 November 2019 в 15:06
поделиться

Части, представленные с типами двоичного числа (т.е. double, float), не могут быть точно сохранены в тех типах.

    Double d = 13.3;        
    BigDecimal bdNotOk = new BigDecimal(d);
    System.out.println("not ok: " + bdNotOk.toString());

    BigDecimal bdNotOk2 = new BigDecimal(13.3);
    System.out.println("not ok2: " + bdNotOk2.toString());

    double x = 13.3;
    BigDecimal ok = BigDecimal.valueOf(x);
    System.out.println("ok: " + ok.toString());

    double y = 13.3;
    // pretty lame, constructor's behavior is different from valueOf static method
    BigDecimal bdNotOk3 = new BigDecimal(y);
    System.out.println("not ok3: " + bdNotOk3.toString());

    BigDecimal ok2 = new BigDecimal("13.3");
    System.out.println("ok2: " + ok2.toString());

    Double e = 0.0;
    for(int i = 0; i < 10; ++i) e = e + 0.1; // some fractions cannot be accurately represented with binary
    System.out.println("not ok4: " + e.toString()); // should be 1


    BigDecimal notOk5 = BigDecimal.valueOf(e);
    System.out.println("not ok5: " + notOk5.toString()); // should be 1

    /* 
     * here are some fractions that can be represented exactly in binary:
     * 0.5   = 0.1   = 1 / 2
     * 0.25  = 0.01  = 1 / 4
     * 0.75  = 0.11  = 3 / 4
     * 0.125 = 0.001 = 1 / 8
     */

вывод:

not ok: 13.300000000000000710542735760100185871124267578125
not ok2: 13.300000000000000710542735760100185871124267578125
ok: 13.3
not ok3: 13.300000000000000710542735760100185871124267578125
ok2: 13.3
not ok4: 0.9999999999999999
not ok5: 0.9999999999999999

Просто использование BigDecimal.valueOf(d) или new BigDecimal(s) .

3
ответ дан 30 November 2019 в 15:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: