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

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

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() и т. д., но библиотека, с которой я работаю, использует toString(), и я не могу это изменить :-(

Ваше здоровье.


person Damo    schedule 20.01.2009    source источник
comment
На самом деле версия toString() - это, вероятно, то, что вам нужно. Десятичные числа не могут быть правильно представлены как двойные, и хотя BigDecimals решают эту проблему, предоставление им двойного числа в конструкторе подрывает решение, поскольку у них нет точного числа, с которого можно начать.   -  person Andrzej Doyle    schedule 20.01.2009


Ответы (5)


Итак, API устраняет это очевидное несоответствие в конструкторе BigDecimal(double val):

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

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

  3. Если в качестве источника для BigDecimal необходимо использовать двойное число, обратите внимание, что этот конструктор обеспечивает точное преобразование; это не дает того же результата, что и преобразование double в String с использованием метода Double.toString(double) и последующего использования конструктора BigDecimal(String). Чтобы получить этот результат, используйте статический метод valueOf(double).

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

person Zach Scrivena    schedule 20.01.2009
comment
+1: Вот как на самом деле работает Double. Не так много общего с BigDecimal. - person S.Lott; 20.01.2009
comment
Хороший. Так что действительно кто-то должен бросить мне RTFM ;-) - person Damo; 20.01.2009
comment
Обратите внимание, что новый BigDecimal(d+) также работает нормально. Результаты в 13.3 точно так же, как при использовании valueOf (d) - person BAERUS; 28.05.2015

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

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

person Michael Borgwardt    schedule 20.01.2009

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

person Bombe    schedule 20.01.2009

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

person Jon Skeet    schedule 20.01.2009

Дроби, представленные типами двоичных чисел (например, 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).

person Michael Buen    schedule 20.01.2009