Muszę obliczyć niektóre zmienne zmiennoprzecinkowe, a mój kolega sugeruje, żebym użył BigDecimal
zamiast tego, double
ponieważ będzie to bardziej precyzyjne. Ale chcę wiedzieć, co to jest i jak najlepiej wykorzystać BigDecimal
?
Muszę obliczyć niektóre zmienne zmiennoprzecinkowe, a mój kolega sugeruje, żebym użył BigDecimal
zamiast tego, double
ponieważ będzie to bardziej precyzyjne. Ale chcę wiedzieć, co to jest i jak najlepiej wykorzystać BigDecimal
?
Odpowiedzi:
A BigDecimal
jest dokładnym sposobem przedstawiania liczb. A Double
ma pewną precyzję. Praca z podwójnymi o różnych wielkościach (powiedzmy d1=1000.0
i d2=0.001
) może spowodować całkowite 0.001
odrzucenie podczas sumowania, ponieważ różnica wielkości jest tak duża. Z BigDecimal
tym by się nie stało.
Wadą BigDecimal
jest 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 Doubles
są wystarczająco dobre.
Polecam czytanie javadoc z BigDecimal
jak robią to wyjaśnić rzeczy lepiej niż ja tutaj :)
if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
BigDecimal
”, Double miałoby więcej „precyzji” (więcej cyfr).
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? ;)
System.out.println(0.003f - 0.002f);
BigDecimal jest dokładny:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
Istnieją dwie główne różnice od podwójnych:
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ś).
Jeśli zapiszesz wartość ułamkową, taką 1 / 7
jak 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/10
lub 1/100
wyraż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.
BigDecimal
Z 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.
BigDecimal
Dodatkową zaletą Javy jest to, że może mieć dowolną (ale skończoną) liczbę cyfr po obu stronach przecinka, ograniczoną tylko dostępną pamięcią.
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.
/* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
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