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

StackOverflow https://stackoverflow.com/questions/460755

  •  19-08-2019
  •  | 
  •  

Вопрос

Как так получается, что 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() и я не могу этого изменить :-(

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

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top