«new BigDecimal (13.3D)» приводит к неточности «13.3000000000000007105 ..»? - PullRequest
16 голосов
/ 20 января 2009

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

Приветствие.

Ответы [ 5 ]

50 голосов
/ 20 января 2009

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

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

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

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

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

11 голосов
/ 20 января 2009

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

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

8 голосов
/ 20 января 2009

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

5 голосов
/ 20 января 2009

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

3 голосов
/ 20 января 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).

...