Mit ‘BigDecimal’ getaggte Artikel

(Ver)rechnen mit Java – Double vs. BigDecimal

Freitag, 17. Juli 2009

Ein einfaches Beispiel:

System.out.println(3 * 0.7);

Die Ausgabe sollte, rein rechnerisch, 2.1 lauten. Probieren Sie es aus. Auf den meisten Rechnern wird das Ergebnis 2.0999999999999996 lauten. Java rechnet hier intern mit Näherungswerten. Das macht die Berechnung zwar performanter aber eben nicht exakt und zudem kann das Ergebnis auf verschiedenen Plattformen unterschiedlich ausfallen (siehe strictfp).

Rechnen mit Strings: BigDecimal

Abhilfe soll hier die Verwendung von java.math.BigDecimal schaffen. Doch auch hier sei Vorsicht geboten:

System.out.println(new BigDecimal(2.1));

Liefert die Ausgabe:
2.100000000000000088817841970012523233890533447265625

Das Beispiel zeigt deutlich mit welchen internen Werten gerechnet wird. Verwenden wir jedoch den Konstruktor mit String erhalten wir den genauen Wert und rechnen auch mit diesem:

System.out.println(new BigDecimal("2.1"));

Die Multiplikation

System.out.println(
   new BigDecimal("3").multiply(new BigDecimal("0.7")));

ergibt dann auch endlich wie erwartet 2.1 und das auf allen Plattformen.

Zum Vergleich zweier Werte sollte die Methode compareTo der equals Methode vorgezogen werden weil sonst scheinbar gleiche Werte mit unterschiedlicher Anzahl an Nachkommastellen (scale) nicht gleich sind.
Ein Beispiel:

new BigDecimal("2.0").equals(new BigDecimal("2.00"))
// false
new BigDecimal("2.0").compareTo(new BigDecimal("2.00")) == 0
// true

Hierzu noch eine kleine Besonderheit. Java unterscheidet 0.0 und -0.0

new Double(0.0).equals(new Double(-0.0))
// false

Bei Verwendung von BigDecimal und der compareTo Methode gibt es hier allerdings keine Unterscheidung.

Was aber tun, wenn die Werte nur als double vorliegen?
Dann kann man sich so behelfen:

BigDecimal value = new BigDecimal(Double.toString(doubleValue));

sollte jedoch die JavaDocs zur toString Methode der Klasse java.lang.Double berücksichtigen.

strictfp

Noch ein kurzer Hinweis zum Schlüsselwort strictfp (das wohl nur wenigen bekannt sein dürfte, da es selten genutzt wird). Mit strictfp können sowohl Klassen als auch Methoden gekennzeichnet werden und versprechen dadurch Rechengenauigkeit nach IEEE-Norm. D.h. zumindest schon mal, dass die Ergebnisse auf verschiedenen Plattform identisch sein werden. Das oben besprochenen Dilemma mit double und float besteht aber weiterhin, auf allen Plattformen wird somit gleich ungenau gerechnet.

Fazit

Zur genauen Berechnung sollte immer BigDecimal(String val) verwendet werden, auch wenn dies zu Lasten der Performance geht. Zudem bietet die Klasse BigDecimal eine Vielzahl praktischer Methoden.
Um sich eine grobe Vorstellung bezüglicher der Performance zu machen, habe ich die Rechnung 3.0 * 0.7 jeweils 100000000 mal mit BigDecimal und double durchgeführt. Dauer:
BigDecimal: 160016 ms
double: 109 ms
Also ein gravierender Unterschied.

Weiterführenden Themen wäre hier u.a. die Klasse java.math.MathContext wenn es um Rundung und die Genauigkeit der Nachkommastellen geht.

Viel Erfolg beim Rechnen.


Christof Aenderl