Double vs. BigDecimal?

303

Muszę obliczyć niektóre zmienne zmiennoprzecinkowe, a mój kolega sugeruje, żebym użył BigDecimalzamiast tego, doubleponieważ będzie to bardziej precyzyjne. Ale chcę wiedzieć, co to jest i jak najlepiej wykorzystać BigDecimal?

Truong Ha
źródło
Sprawdź ten; stackoverflow.com/questions/322749/…
Espen Schulstad,

Odpowiedzi:

446

A BigDecimaljest dokładnym sposobem przedstawiania liczb. A Doublema pewną precyzję. Praca z podwójnymi o różnych wielkościach (powiedzmy d1=1000.0i d2=0.001) może spowodować całkowite 0.001odrzucenie podczas sumowania, ponieważ różnica wielkości jest tak duża. Z BigDecimaltym by się nie stało.

Wadą BigDecimaljest to, że jest wolniejszy i nieco trudniej jest zaprogramować algorytmy w ten sposób (z powodu przeciążenia + - *i /bez przeciążenia).

Jeśli masz do czynienia z pieniędzmi lub precyzja jest koniecznością, użyj BigDecimal. W przeciwnym razie Doublessą wystarczająco dobre.

Polecam czytanie javadoc z BigDecimaljak robią to wyjaśnić rzeczy lepiej niż ja tutaj :)

extraneon
źródło
Tak, obliczam cenę towaru, więc uważam, że BigDecimal jest przydatny w tym przypadku.
Truong Ha,
5
@Truong Ha: Podczas pracy z cenami chcesz korzystać z BigDecimal. A jeśli przechowujesz je w bazie danych, potrzebujesz czegoś podobnego.
extraneon
98
Mówienie, że „BigDecimal jest dokładnym sposobem przedstawiania liczb” jest mylące. 1/3 i 1/7 nie mogą być wyrażone dokładnie w systemie liczb podstawowych 10 (BigDecimal) lub w systemie liczb podstawowych 2 (liczba zmiennoprzecinkowa lub podwójna). 1/3 może być dokładnie wyrażona w podstawie 3, podstawie 6, podstawie 9, podstawie 12 itd., A 1/7 może być wyrażona dokładnie w podstawie 7, podstawie 14, podstawie 21 itp. Zaletą BigDecimal jest to, że jest to arbitralna precyzja i że ludzie są przyzwyczajeni do błędów zaokrąglania, które pojawiają się w bazie 10.
procrastinate_later
3
Dobrze, że jest wolniejszy, pomaga mi zrozumieć, dlaczego kod usługi równoważenia obciążenia wstążki Netflix if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
radzi sobie
@extraneon Myślę, że chcesz powiedzieć „jeśli dokładność jest koniecznością, użyj BigDecimal”, Double miałoby więcej „precyzji” (więcej cyfr).
jspinella
164

Mój angielski nie jest dobry, więc napiszę tutaj prosty przykład.

    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);

Wyjście programu:

0.009999999999999998
0.01

Ktoś nadal chce używać podwójnie? ;)

Bazylia
źródło
11
@eldjon To nieprawda, spójrz na ten przykład: BigDecimal two = new BigDecimal („2”); BigDecimal osiem = nowy BigDecimal („8”); System.out.println (two.divide (eight)); To drukuje 0,25.
Ludvig W
4
podwaja się: D
vach
Niemniej jednak, jeśli zamiast tego użyjesz
liczby
3
@EliuX Float może współpracować z 0,03-0,02, ale inne wartości są wciąż nieprecyzyjne: System.out.println(0.003f - 0.002f);BigDecimal jest dokładny:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
Martin
50

Istnieją dwie główne różnice od podwójnych:

  • Arbitralna precyzja, podobnie jak BigInteger, mogą zawierać dowolną precyzję i rozmiar
  • Podstawa 10 zamiast Podstawa 2, BigDecimal ma skalę n * 10 ^, gdzie n jest dowolną dużą liczbą całkowitą ze znakiem, a skalę można traktować jako liczbę cyfr do przesunięcia punktu dziesiętnego w lewo lub w prawo

Powodem, dla którego powinieneś używać BigDecimal do obliczeń pieniężnych, nie jest to, że może on reprezentować dowolną liczbę, ale że może reprezentować wszystkie liczby, które mogą być reprezentowane w postaci dziesiętnej i które obejmują praktycznie wszystkie liczby w świecie monetarnym (nigdy nie przenosisz 1/3 $ do kogoś).

Meros
źródło
2
Ta odpowiedź naprawdę wyjaśnia różnicę i powód używania BigDecimal nad podwójnym. Obawy dotyczące wydajności są drugorzędne.
Vortex,
To nie jest w 100% prawda. Napisałeś, że BigDecimal ma skalę „n * 10 ^”. Java robi to tylko dla liczb ujemnych. Tak poprawne byłoby: „unscaledValue × 10 ^ -scale”. W przypadku liczb dodatnich BigDecimal składa się z „nieskalowanej liczby całkowitej o dowolnej precyzji i 32-bitowej skali całkowitej”, podczas gdy skala to liczba cyfr po prawej stronie przecinka dziesiętnego.
ręka NOD
25

Jeśli zapiszesz wartość ułamkową, taką 1 / 7jak wartość dziesiętna, otrzymasz

1/7 = 0.142857142857142857142857142857142857142857...

z nieskończoną sekwencją 142857 . Ponieważ możesz wpisać tylko skończoną liczbę cyfr, nieuchronnie wprowadzisz błąd zaokrąglania (lub obcięcia).

Liczby takie jak 1/10lub 1/100wyrażone jako liczby binarne z częścią ułamkową mają również nieskończoną liczbę cyfr po przecinku:

1/10 = binary 0.0001100110011001100110011001100110...

Doubles przechowuj wartości jako binarne i dlatego może wprowadzić błąd wyłącznie poprzez konwersję liczby dziesiętnej na liczbę binarną, nawet bez wykonywania arytmetyki.

BigDecimalZ drugiej strony liczby dziesiętne (jak ) przechowują każdą cyfrę dziesiętną taką, jaka jest. Oznacza to, że typ dziesiętny nie jest bardziej precyzyjny niż binarny zmiennoprzecinkowy lub typ punktu stałego w sensie ogólnym (tzn. Nie może przechowywać1/7 bez utraty precyzji), ale jest bardziej dokładny w przypadku liczb, które mają skończoną liczbę cyfr dziesiętnych jako często dotyczy to kalkulacji pieniężnych.

BigDecimalDodatkową zaletą Javy jest to, że może mieć dowolną (ale skończoną) liczbę cyfr po obu stronach przecinka, ograniczoną tylko dostępną pamięcią.

Olivier Jacot-Descombes
źródło
7

BigDecimal to biblioteka numeryczna Oracle o dowolnej precyzji. BigDecimal jest częścią języka Java i jest użyteczny w różnych aplikacjach, od finansowych po naukowe (tam gdzie jestem).

Nie ma nic złego w stosowaniu podwójnych metod do niektórych obliczeń. Załóżmy jednak, że chcesz obliczyć Math.Pi * Math.Pi / 6, czyli wartość funkcji Riemann Zeta dla prawdziwego argumentu dwóch (projekt, nad którym obecnie pracuję). Podział zmiennoprzecinkowy przedstawia bolesny problem błędu zaokrąglania.

Z drugiej strony BigDecimal zawiera wiele opcji obliczania wyrażeń z dowolną dokładnością. Metody dodawania, mnożenia i dzielenia opisane w dokumentacji Oracle poniżej „zajmują miejsce +, * i / w BigDecimal Java World:

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

Metoda CompareTo jest szczególnie przydatna w pętli while i for.

Należy jednak zachować ostrożność podczas korzystania z konstruktorów dla BigDecimal. Konstruktor ciągów jest bardzo przydatny w wielu przypadkach. Na przykład kod

BigDecimal onethird = nowy BigDecimal („0.33333333333”);

wykorzystuje reprezentację ciągu 1/3 do reprezentowania tej nieskończenie powtarzającej się liczby z określonym stopniem dokładności. Błąd zaokrąglenia jest najprawdopodobniej gdzieś tak głęboko w JVM, że błędy zaokrąglenia nie zakłócają większości twoich praktycznych obliczeń. Jednak z własnego doświadczenia widziałem, jak zaokrągla się w górę. Metoda setScale jest ważna pod tym względem, jak widać w dokumentacji Oracle.

rybak
źródło
BigDecimal jest część z Java arbitralny precyzji biblioteki numerycznej. „Wewnętrzny” jest w tym kontekście raczej pozbawiony znaczenia, zwłaszcza, że ​​został napisany przez IBM.
Markiz Lorne
@EJP: Zajrzałem do klasy BigDecimal i dowiedziałem się, że tylko jej część jest napisana przez IBM. Komentarz dotyczący praw autorskich poniżej: /* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
realPK
7

Jeśli masz do czynienia z obliczeniami, istnieją przepisy dotyczące sposobu obliczania i jakiej precyzji należy użyć. Jeśli ci się nie uda, zrobisz coś nielegalnego. Jedynym prawdziwym powodem jest to, że reprezentacja bitowa przypadków dziesiętnych nie jest precyzyjna. Jak to ujął Basil, przykład jest najlepszym wytłumaczeniem. Aby uzupełnić jego przykład, oto co się dzieje:

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)));
}

Wynik:

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

Mamy też to:

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)));
}

Daje nam wynik:

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

Ale:

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)));
}

Ma wynik:

BigDec:  10 / 3 = 3.3333 
jfajunior
źródło