Double vs. BigDecimal?

303

Ich muss einige Gleitkommavariablen berechnen und mein Kollege schlägt mir vor, sie BigDecimalstattdessen zu verwenden, doubleda sie genauer sind. Aber ich möchte wissen, was es ist und wie ich das Beste daraus machen kann BigDecimal?

Truong Ha
quelle
Schauen Sie sich dieses an; stackoverflow.com/questions/322749/…
Espen Schulstad

Antworten:

446

A BigDecimalist eine exakte Art, Zahlen darzustellen. A Doublehat eine gewisse Präzision. Das Arbeiten mit Doppelwerten verschiedener Größen (z. B. d1=1000.0und d2=0.001) kann dazu führen 0.001, dass beim Summieren insgesamt ein Abfall auftritt, da der Größenunterschied so groß ist. Damit BigDecimalwürde das nicht passieren.

Der Nachteil BigDecimalist , 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 Doublesneigen sie dazu, gut genug zu sein.

Ich empfehle das Javadoc von zu lesen, BigDecimalda sie die Dinge besser erklären als ich hier :)

Extraneon
quelle
Ja, ich berechne den Aktienkurs, daher glaube ich, dass BigDecimal in diesem Fall nützlich ist.
Truong Ha
5
@Truong Ha: Wenn Sie mit Preisen arbeiten, möchten Sie BigDecimal verwenden. Und wenn Sie sie in der Datenbank speichern, möchten Sie etwas Ähnliches.
extraneon
98
Zu sagen, dass "BigDecimal eine exakte Art der Darstellung von Zahlen ist", ist irreführend. 1/3 und 1/7 können nicht genau in einem Basis-10-Zahlensystem (BigDecimal) oder in einem Basis-2-Zahlensystem (Float oder Double) ausgedrückt werden. 1/3 könnte genau in Basis 3, Basis 6, Basis 9, Basis 12 usw. ausgedrückt werden, und 1/7 könnte genau in Basis 7, Basis 14, Basis 21 usw. ausgedrückt werden. BigDecimal-Vorteile sind die willkürliche Genauigkeit und dass die Menschen auf die Rundungsfehler verwendet werden , erhalten Sie in der Basis 10
procrastinate_later
3
Der gute Punkt, dass es langsamer ist, hilft mir zu verstehen, warum der Netflix Ribbon Load Balancer-Code mit Doppelwerten if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
umgeht
@extraneon Ich denke, Sie wollen sagen "Wenn Genauigkeit ein Muss ist, verwenden Sie BigDecimal", hätte ein Double mehr "Präzision" (mehr Ziffern).
Jspinella
164

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

Basilikum
quelle
11
@eldjon Das stimmt nicht. Sehen Sie sich dieses Beispiel an: BigDecimal two = new BigDecimal ("2"); BigDecimal acht = neues BigDecimal ("8"); System.out.println (two.divide (acht)); Dies druckt 0,25 aus.
Ludvig W
4
Doppel forevr: D
vach
Wenn Sie stattdessen einen Float verwenden, erhalten Sie in diesem Fall die gleiche Präzision wie BigDecimal, aber eine
weitaus
3
@ EliX Float kann mit 0.03-0.02 arbeiten, aber andere Werte sind immer noch ungenau: System.out.println(0.003f - 0.002f);BigDecimal ist genau:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
Martin
50

Es gibt zwei Hauptunterschiede zum Doppel:

  • Beliebige Genauigkeit, ähnlich wie BigInteger, können sie eine beliebige Anzahl von Genauigkeit und Größe enthalten
  • Basis 10 anstelle von Basis 2 ist eine BigDecimal-Skala n * 10 ^, wobei n eine beliebige große Ganzzahl mit Vorzeichen ist und die Skalierung als die Anzahl der Stellen angesehen werden kann, um den Dezimalpunkt nach links oder rechts zu verschieben

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

Meros
quelle
2
Diese Antwort erklärt wirklich den Unterschied und den Grund für die Verwendung von BigDecimal über Double. Leistungsbedenken sind zweitrangig.
Vortex
Dies ist nicht 100% wahr. Sie haben geschrieben, dass ein BigDecimal "n * 10 ^ scale" ist. Java macht das nur für negative Zahlen. So richtig wäre: "unscaledValue × 10 ^ -scale". Bei positiven Zahlen besteht das BigDecimal aus einem "nicht skalierten ganzzahligen Wert mit beliebiger Genauigkeit und einer 32-Bit-Ganzzahlskala", während die Skala die Anzahl der Stellen rechts vom Dezimalpunkt ist.
Die Hand von NOD
25

Wenn Sie einen Bruchwert wie einen 1 / 7Dezimalwert 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/10oder 1/100ausgedrü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/7ohne 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 BigDecimalhat 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.

Olivier Jacot-Descombes
quelle
7

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.

Fischerhut
quelle
BigDecimal ist Teil der numerischen Bibliothek mit beliebiger Genauigkeit von Java . 'Inhouse' ist in diesem Zusammenhang ziemlich bedeutungslos, insbesondere da es von IBM geschrieben wurde.
Marquis von Lorne
@EJP: Ich habe mir die BigDecimal-Klasse angesehen und festgestellt, dass nur ein Teil davon von IBM geschrieben wurde. Copyright Kommentar unten: /* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
realPK
7

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 
jfajunior
quelle