Ich muss einige Gleitkommavariablen berechnen und mein Kollege schlägt mir vor, sie BigDecimal
stattdessen zu verwenden, double
da sie genauer sind. Aber ich möchte wissen, was es ist und wie ich das Beste daraus machen kann BigDecimal
?
Ich muss einige Gleitkommavariablen berechnen und mein Kollege schlägt mir vor, sie BigDecimal
stattdessen zu verwenden, double
da sie genauer sind. Aber ich möchte wissen, was es ist und wie ich das Beste daraus machen kann BigDecimal
?
Antworten:
A BigDecimal
ist eine exakte Art, Zahlen darzustellen. A Double
hat eine gewisse Präzision. Das Arbeiten mit Doppelwerten verschiedener Größen (z. B. d1=1000.0
und d2=0.001
) kann dazu führen 0.001
, dass beim Summieren insgesamt ein Abfall auftritt, da der Größenunterschied so groß ist. Damit BigDecimal
würde das nicht passieren.
Der Nachteil BigDecimal
ist , dass es langsamer, und es ist ein bisschen schwieriger zu Programmalgorithmen auf diese Weise (wegen +
-
*
und /
nicht überlastet wird).
Wenn Sie mit Geld zu tun haben oder Präzision ein Muss ist, verwenden Sie BigDecimal
. Ansonsten Doubles
neigen sie dazu, gut genug zu sein.
Ich empfehle das Javadoc von zu lesen, BigDecimal
da sie die Dinge besser erklären als ich hier :)
if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
BigDecimal
", hätte ein Double mehr "Präzision" (mehr Ziffern).
Mein Englisch ist nicht gut, deshalb schreibe ich hier nur ein einfaches Beispiel.
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);
Programmausgabe:
0.009999999999999998
0.01
Möchte noch jemand double verwenden? ;)
System.out.println(0.003f - 0.002f);
BigDecimal ist genau:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
Es gibt zwei Hauptunterschiede zum Doppel:
Der Grund, warum Sie BigDecimal für Geldberechnungen verwenden sollten, ist nicht, dass es eine beliebige Zahl darstellen kann, sondern dass es alle Zahlen darstellen kann, die in Dezimalzahlen dargestellt werden können und praktisch alle Zahlen in der Geldwelt enthalten (Sie überweisen niemals 1/3 $ für jemanden).
Wenn Sie einen Bruchwert wie einen 1 / 7
Dezimalwert aufschreiben, erhalten Sie
1/7 = 0.142857142857142857142857142857142857142857...
mit einer unendlichen Folge von 142857
. Da Sie nur eine endliche Anzahl von Ziffern schreiben können, wird zwangsläufig ein Rundungs- (oder Kürzungs-) Fehler auftreten.
Zahlen wie 1/10
oder 1/100
ausgedrückt als Binärzahlen mit einem Bruchteil haben auch eine unendliche Anzahl von Stellen nach dem Dezimalpunkt:
1/10 = binary 0.0001100110011001100110011001100110...
Doubles
Speichern Sie Werte als binär und führen Sie daher möglicherweise einen Fehler ein, indem Sie eine Dezimalzahl in eine Binärzahl konvertieren, ohne eine Arithmetik durchzuführen.
Dezimalzahlen (wie BigDecimal
) hingegen speichern jede Dezimalstelle unverändert. Dies bedeutet, dass ein Dezimaltyp im Allgemeinen nicht genauer ist als ein binärer Gleitkomma- oder Festkommatyp (dh er kann nicht 1/7
ohne Genauigkeitsverlust gespeichert werden), aber er ist genauer für Zahlen mit einer endlichen Anzahl von Dezimalstellen als ist häufig bei Geldberechnungen der Fall.
Java BigDecimal
hat den zusätzlichen Vorteil, dass es auf beiden Seiten des Dezimalpunkts eine beliebige (aber endliche) Anzahl von Stellen haben kann, die nur durch den verfügbaren Speicher begrenzt ist.
BigDecimal ist die numerische Bibliothek von Oracle mit beliebiger Genauigkeit. BigDecimal ist Teil der Java-Sprache und eignet sich für eine Vielzahl von Anwendungen, die von finanziell bis wissenschaftlich reichen (genau dort bin ich).
Es ist nichts Falsches daran, für bestimmte Berechnungen Doppel zu verwenden. Angenommen, Sie wollten Math.Pi * Math.Pi / 6 berechnen, dh den Wert der Riemann-Zeta-Funktion für ein reales Argument von zwei (ein Projekt, an dem ich gerade arbeite). Die Gleitkommadivision stellt Sie vor ein schmerzhaftes Problem mit Rundungsfehlern.
BigDecimal hingegen enthält viele Optionen zum Berechnen von Ausdrücken mit beliebiger Genauigkeit. Die in der folgenden Oracle-Dokumentation beschriebenen Methoden zum Hinzufügen, Multiplizieren und Teilen "ersetzen" von +, * und / oder in BigDecimal Java World:
http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html
Die compareTo-Methode ist besonders nützlich in while- und for-Schleifen.
Seien Sie jedoch vorsichtig bei der Verwendung von Konstruktoren für BigDecimal. Der String-Konstruktor ist in vielen Fällen sehr nützlich. Zum Beispiel der Code
BigDecimal onethird = new BigDecimal ("0.33333333333");
verwendet eine Zeichenfolgendarstellung von 1/3, um diese sich unendlich wiederholende Zahl mit einem bestimmten Genauigkeitsgrad darzustellen. Der Rundungsfehler liegt höchstwahrscheinlich irgendwo so tief in der JVM, dass die Rundungsfehler die meisten Ihrer praktischen Berechnungen nicht stören. Ich habe jedoch aus persönlicher Erfahrung gesehen, wie sich eine Abrundung eingeschlichen hat. Die setScale-Methode ist in dieser Hinsicht wichtig, wie aus der Oracle-Dokumentation hervorgeht.
/* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
Wenn Sie sich mit Berechnungen beschäftigen, gibt es Gesetze, wie Sie berechnen und welche Genauigkeit Sie verwenden sollten. Wenn Sie dies nicht tun, werden Sie etwas Illegales tun. Der einzige wirkliche Grund ist, dass die Bitdarstellung von Dezimalfällen nicht präzise ist. Wie Basil einfach ausgedrückt hat, ist ein Beispiel die beste Erklärung. Um sein Beispiel zu ergänzen, passiert Folgendes:
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)));
}
Ausgabe:
Double: 0,3 - 0,2 = 0.09999999999999998
Float: 0,3 - 0,2 = 0.10000001
BigDec: 0,3 - 0,2 = 0.1
Auch das haben wir:
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)));
}
Gibt uns die Ausgabe:
Double: 10 / 3 = 3.3333333333333335
Float: 10 / 3 = 3.3333333
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion
Aber:
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)));
}
Hat die Ausgabe:
BigDec: 10 / 3 = 3.3333