Двойной против BigDecimal?

304

Я должен вычислить некоторые переменные с плавающей точкой, и мой коллега предлагает мне использовать BigDecimalвместо, doubleтак как это будет более точным. Но я хочу знать, что это такое и как извлечь из этого максимум BigDecimal?

Чыонг Ха
источник
Проверьте это; stackoverflow.com/questions/322749/…
Эспен Шульстад

Ответы:

446

A BigDecimalявляется точным способом представления чисел. А Doubleимеет определенную точность. Работа с двойными значениями различной величины (скажем, d1=1000.0и d2=0.001) может привести к 0.001полному падению при суммировании, поскольку разница в величине очень велика. С BigDecimalэтим бы не случилось.

Недостатком BigDecimalявляется то , что он медленнее, и это немного сложнее программных алгоритмов , что путь (из - за + - *и /не перегружена).

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

Я рекомендую читать Javadoc о , BigDecimalкак они объясняют вещи лучше , чем я здесь :)

extraneon
источник
Да, я рассчитываю цену на акции, поэтому я считаю, что BigDecimal полезен в этом случае.
Чыонг Ха
5
@Truong Ha: При работе с ценами вы хотите использовать BigDecimal. И если вы храните их в базе данных, вы хотите что-то подобное.
Extraneon
98
Сказать, что «BigDecimal - это точный способ представления чисел», вводит в заблуждение. 1/3 и 1/7 не могут быть выражены точно в системе счисления с основанием 10 (BigDecimal) или в системе счисления с основанием 2 (с плавающей или двойной). 1/3 может быть точно выражено в базе 3, базе 6, базе 9, базе 12 и т. Д., А 1/7 может быть точно выражено в базе 7, базе 14, базе 21 и т. Д. Преимущества BigDecimal в том, что это произвольная точность и что люди привыкли к ошибкам округления, которые вы получаете в базе 10.
procrastinate_later
3
Хорошая мысль о том, что он медленнее, помогает мне понять, почему код балансировки нагрузки ленты Netflix работает с if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
двойными
@extraneon Я думаю, вы хотите сказать «если точность необходима, используйте BigDecimal», у Double будет больше «точности» (больше цифр).
Jspinella
164

Мой английский не очень хороший, поэтому я просто напишу простой пример здесь.

    double a = 0.02;
    double b = 0.03;
    double c = b - a;
    System.out.println(c);

    BigDecimal _a = new BigDecimal("0.02");
    BigDecimal _b = new BigDecimal("0.03");
    BigDecimal _c = _b.subtract(_a);
    System.out.println(_c);

Выход программы:

0.009999999999999998
0.01

Кто-нибудь еще хочет использовать double? ;)

Бэзил
источник
11
@eldjon Это не правда, посмотрите на этот пример: BigDecimal two = new BigDecimal ("2"); BigDecimal восемь = новый BigDecimal ("8"); System.out.println (two.divide (восемь)); Это печатает 0,25.
Людвиг
4
удваивает forevr: D
Vach
Тем не менее, если вы используете вместо этого float, вы получите ту же точность, что и BigDecimal в этом случае, но гораздо лучшую производительность
EliuX
3
@EliuX Float может работать с 0.03-0.02, но другие значения все еще неточны: System.out.println(0.003f - 0.002f);BigDecimal является точным:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
Martin
50

Есть два основных отличия от двойного:

  • Произвольная точность, подобно BigInteger они могут содержать число произвольной точности и размера
  • Base 10 вместо Base 2, BigDecimal - это масштаб n * 10 ^, где n - произвольное большое целое число со знаком, а масштаб можно рассматривать как количество цифр для перемещения десятичной точки влево или вправо

Причина, по которой вы должны использовать BigDecimal для денежных расчетов, заключается не в том, что он может представлять любое число, а в том, что он может представлять все числа, которые могут быть представлены в десятичном представлении и которые включают в себя практически все числа в денежном мире (вы никогда не переводите 1/3 $ кому-то).

Мерос
источник
2
Этот ответ действительно объясняет разницу и причину использования BigDecimal над двойным. Проблемы производительности являются вторичными.
Вихрь
Это не на 100% верно. Вы написали, что BigDecimal - это "n * 10 ^ scale". Java делает это только для отрицательных чисел. Так правильно будет: "unscaledValue × 10 ^ -scale". Для положительных чисел BigDecimal состоит из «целочисленного немасштабированного значения произвольной точности и 32-битной целочисленной шкалы», тогда как шкала - это число цифр справа от десятичной точки.
рука NOD
25

Если вы записываете дробное значение, такое 1 / 7как десятичное значение, вы получите

1/7 = 0.142857142857142857142857142857142857142857...

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

Числа, подобные 1/10или 1/100выраженные в виде двоичных чисел с дробной частью, также имеют бесконечное количество цифр после десятичной точки:

1/10 = binary 0.0001100110011001100110011001100110...

Doubles хранить значения в двоичном виде и, следовательно, может привести к ошибке исключительно путем преобразования десятичного числа в двоичное число, даже не выполняя никакой арифметики.

С BigDecimalдругой стороны, десятичные числа (как ) сохраняют каждую десятичную цифру как есть. Это означает, что десятичный тип не является более точным, чем двоичный тип с плавающей запятой или тип с фиксированной запятой в общем смысле (т. Е. Он не может быть сохранен 1/7без потери точности), но он более точен для чисел с конечным числом десятичных разрядов, так как это часто бывает для расчета денег.

У Java BigDecimalесть дополнительное преимущество, заключающееся в том, что он может иметь произвольное (но конечное) количество цифр с обеих сторон десятичной точки, ограниченное только доступной памятью.

Оливье Жако-Дескомб
источник
7

BigDecimal - это числовая библиотека Oracle с произвольной точностью. BigDecimal является частью языка Java и полезен для самых разных приложений - от финансовых до научных (вот где я).

Нет ничего плохого в использовании двойников для определенных вычислений. Предположим, однако, что вы хотите вычислить Math.Pi * Math.Pi / 6, то есть значение дзета-функции Римана для реального аргумента два (проект, над которым я сейчас работаю). Деление с плавающей точкой ставит вас перед болезненной проблемой ошибки округления.

BigDecimal, с другой стороны, включает в себя множество опций для вычисления выражений с произвольной точностью. Методы добавления, умножения и деления, описанные в документации Oracle ниже, «заменяют» +, * и / в BigDecimal Java World:

http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html

Метод CompareTo особенно полезен в циклах while и for.

Однако будьте осторожны при использовании конструкторов для BigDecimal. Конструктор строк очень полезен во многих случаях. Например, код

BigDecimal onethird = new BigDecimal ("0.33333333333");

использует строковое представление 1/3 для представления этого бесконечно повторяющегося числа с заданной степенью точности. Ошибка округления, скорее всего, где-то так глубоко внутри JVM, что ошибки округления не будут мешать большинству ваших практических расчетов. Однако, по личному опыту, я видел, как округление приближается. В этом отношении метод setScale важен, как видно из документации Oracle.

fishermanhat
источник
BigDecimal является частью из Явы произвольной точности библиотеки численной. «Внутренний» в этом контексте довольно бессмыслен, особенно в том смысле, как он был написан IBM.
маркиз Лорн
@EJP: я изучил класс BigDecimal и узнал, что только часть его написана IBM. Комментарий авторского права ниже: /* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
realPK
7

Если вы имеете дело с расчетами, существуют законы о том, как вы должны рассчитывать и какую точность вы должны использовать. Если вам не удастся, вы будете делать что-то незаконное. Единственная реальная причина в том, что битовое представление десятичных регистров не является точным. Как сказал Бэзил, пример - лучшее объяснение. Просто чтобы дополнить его пример, вот что происходит:

static void theDoubleProblem1() {
    double d1 = 0.3;
    double d2 = 0.2;
    System.out.println("Double:\t 0,3 - 0,2 = " + (d1 - d2));

    float f1 = 0.3f;
    float f2 = 0.2f;
    System.out.println("Float:\t 0,3 - 0,2 = " + (f1 - f2));

    BigDecimal bd1 = new BigDecimal("0.3");
    BigDecimal bd2 = new BigDecimal("0.2");
    System.out.println("BigDec:\t 0,3 - 0,2 = " + (bd1.subtract(bd2)));
}

Вывод:

Double:  0,3 - 0,2 = 0.09999999999999998
Float:   0,3 - 0,2 = 0.10000001
BigDec:  0,3 - 0,2 = 0.1

Также у нас есть что:

static void theDoubleProblem2() {
    double d1 = 10;
    double d2 = 3;
    System.out.println("Double:\t 10 / 3 = " + (d1 / d2));

    float f1 = 10f;
    float f2 = 3f;
    System.out.println("Float:\t 10 / 3 = " + (f1 / f2));

    // Exception! 
    BigDecimal bd3 = new BigDecimal("10");
    BigDecimal bd4 = new BigDecimal("3");
    System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4)));
}

Дает нам вывод:

Double:  10 / 3 = 3.3333333333333335
Float:   10 / 3 = 3.3333333
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion

Но:

static void theDoubleProblem2() {
    BigDecimal bd3 = new BigDecimal("10");
    BigDecimal bd4 = new BigDecimal("3");
    System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4, 4, BigDecimal.ROUND_HALF_UP)));
}

Имеет выход:

BigDec:  10 / 3 = 3.3333 
jfajunior
источник