Подвійний проти BigDecimal?


303

Я повинен обчислити деякі змінні з плаваючою комою, і мій колега пропонує мені використовувати BigDecimalзамість цього, doubleоскільки це буде більш точним. Але я хочу знати, що це таке, і як із цього зробити найбільше BigDecimal?


Відповіді:


446

A BigDecimal- це точний спосіб представлення чисел. A Doubleмає певну точність. Робота з подвійними різними величинами (скажімо d1=1000.0і d2=0.001) може призвести до того, 0.001що при підсумовуванні цілком знизиться, оскільки різниця у величині настільки велика. З BigDecimalцим не сталося б.

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

Якщо ви маєте справу з грошима, або точність є обов'язковою, використовуйте BigDecimal. Інакше, Doublesяк правило, досить добре.

Я рекомендую читати Javadoc про , BigDecimalяк вони пояснюють речі краще , ніж я тут :)


Так, я розраховую ціну акцій, тому вважаю, що BigDecimal корисний у цьому випадку.
Truong Ha

5
@Truong Ha: Працюючи з цінами, ви хочете використовувати BigDecimal. І якщо ви зберігаєте їх у базі даних, ви хочете щось подібне.
екстранеон

98
Сказати, що "BigDecimal - це точний спосіб представлення чисел", вводить в оману. 1/3 та 1/7 не можуть бути виражені точно в базовій системі числення 10 (BigDecimal) або в системі числення бази 2 (float або double). 1/3 може бути точно виражено у базі 3, базі 6, базі 9, базі 12 тощо, а 1/7 може бути виражена саме у базі 7, базі 14, базі 21 тощо. Перевагами BigDecimal є те, що це довільна точність і що люди звикли до помилок округлення, які ви отримуєте в базі 10.
procrastinate_later

3
Хороший момент про те, що він повільніше, допомагає мені зрозуміти, чому код балансира навантаження на стрічку Netflix має подвійний характер, а потім має такі рядки:if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
michaelok

@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

Хтось все ще хоче використовувати подвійний? ;)


11
@eldjon Це неправда. Подивіться на цей приклад: BigDecimal two = новий BigDecimal ("2"); BigDecimal вісім = новий BigDecimal ("8"); System.out.println (two.divide (вісім)); Це друкує 0,25.
Ludvig W

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")));
Мартін

50

Є дві основні відмінності від подвійної:

  • Довільна точність, подібно до BigInteger, вони можуть містити кількість довільної точності та розміру
  • База 10 замість Бази 2, BigDecimal - шкала n * 10 ^, де n - довільне велике ціле число, підписане, і масштаб можна вважати числом цифр для переміщення десяткової крапки вліво або вправо

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


2
Ця відповідь справді пояснює різницю та причину використання BigDecimal над подвійним. Побоювання щодо ефективності є другорядними.
Vortex

Це не на 100% правда. Ви писали, що BigDecimal - це "n * 10 ^ шкала". Java робить це лише для від'ємних чисел. Таким правильним було б: "unscaledValue × 10 ^ -scale". Для позитивних чисел BigDecimal складається з "нецільового значення довільної точності цілого числа та 32-бітової цілочисельної шкали", тоді як шкала - це число цифр праворуч від десяткової коми.
рука НОД

25

Якщо ви записуєте дробове значення на зразок 1 / 7десяткового значення, яке ви отримуєте

1/7 = 0.142857142857142857142857142857142857142857...

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

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

1/10 = binary 0.0001100110011001100110011001100110...

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

Десяткові числа (як BigDecimal ), з іншого боку, зберігають кожну десяткову цифру як є. Це означає, що десятковий тип не є більш точним, ніж двійкова плаваюча точка або тип фіксованої точки у загальному значенні (тобто він не може зберігатись 1/7без втрати точності), але він більш точний для чисел, які мають кінцеве число десяткових цифр як часто трапляється для грошових розрахунків.

BigDecimalДодаткова перевага Java має те, що вона може мати довільну (але кінцеву) кількість цифр з обох сторін десяткової крапки, обмежену лише наявною пам'яттю.


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 особливо корисний для циклів під час та для нього.

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

BigDecimal onethird = новий BigDecimal ("0,33333333333");

використовує рядкове подання 1/3, щоб представити це нескінченно повторюване число із заданою ступенем точності. Похибка округлення, швидше за все, знаходиться десь так глибоко всередині JVM, що помилки заокруглення не будуть порушувати більшість практичних розрахунків. Я, з особистого досвіду, бачив кругле повзання. Метод setScale важливий у цьому відношенні, як видно з документації Oracle.


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 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.