BigDecimal equals

Hallo zusammen,

ich bekomme Werte aus einer XML-Datei, diese halte ich als BigDecimal-Zahlen im Programm. Später werden diese Werte in eine Oracle-Datenbank geschrieben und dort wieder als BigDecimal ausgelesen.

Vergleiche ich nun die Werte aus der DB mit denen aus der XML-Datei, werden diese nicht als gleich erkannt obwohl sie es sind.

Wert aus XML: 4.28183513045136E-04
Wert im Programm: 0.0004281835130451360
Wert aus DB: 0.000428183513045136

Das Problem ist, dass die füllenden 0 am Ende fehlen, wenn die Werte aus der Datenbank kommen.

Laut Java-Doc ist dieses Verhalten richtig für BigDecimal:

Compares this BigDecimal with the specified Object for equality. Unlike compareTo, this method considers two BigDecimal objects equal only if they are equal in value and scale (thus 2.0 is not equal to 2.00 when compared by this method).

Meine Frage dazu, wie kann ich das umgehen? Einfach immer die füllenden 0 am Ende zu entfernen ist mir zu umständlich, da ich insgesamt immer 12 Werte vergleichen muss. (Sollte es anders nicht machbar sein, muss ich den sauren Apfel beißen)

Ist ein Vergleich mit compareTo eine Alternative?

    BigDecimal a;
    BigDecimal b;
    public boolean equals(Test other){
        if(this.a.compareTo(other.a)==0 && this.b.compareTo(other.b)==0){
            return true;
        }
    }
}```

Irgendwie hab ich da gerade ne Blockade beim Denken. :ka:

Wenn Du es selbst in der Hand hast, anstatt mit equals mit compareTo zu vergleichen, dann ist das ein ganbarer Weg. Bei Collections/Maps hat man es aber leider nicht in der Hand. Hier würde ein Umstieg auf TreeXXX-Implementierungen Abhilfe schaffen. Wenn das aus bestimmten Gründen unerwünscht ist, musst Du wohl doch in den sauren Apfel beißen und Deine BigDecimals nach dem parsen des XML normalisieren.

P.S. Und bei den Standard-Implementierungen von List (Array- und Linked) gibt es garkeine Abhilfe.

der Thementitel in eine Suchmaschine findet einige Artikel wie

falls interessiert

was hat die Zahl 12 zu sagen, doch wohl hoffentlich nicht 12 einzelne Lese-Parse-Vorgänge jeweils um mehrere Zeilen Code zu ergänzen?
sowas gehört in eine Methode, parseBigD18(String) oder so für 18 Nachkommastellen, wenn das für die DB genau festgelegt ist,
gibt es auch keine 19. Stelle, Rundungsfragen?

die Anzahl der Nachkommastellen ist übrigens mit der setScale()-Methode recht schnell gesetzt, falls umständlicheres gedacht,
(Achtung, liefert wie alles ein neues BigDecimal-Objekt, verändert nichts)
setScale(25) auf XML- und DB-Werte wäre hier eine recht ungefährliche Sache, egal wie viele Nachkommastellen konkret in XML oder DB
(bis 25…, richtig hohe Werte wie 100 wären ja auch mal was :wink: )

an zwei Stellen im Programm, und evtl. fertig

Wrapper sind auch immer noch eine Erwähnung wert, nicht BigDecimal in die Liste sondern eigene Container-Klassen mit besserer equals

Um etwas konkreter zu werden, wir haben eine Klasse die eine Position samt Drehmatrix im Raum darstellt. Dort werden in der equals-Methode genau diese 12 Werte verglichen.

Beim Lesen aus dem XML wird auf 19 Nachkommastellen gesetzt, die Felder selbst sind für Hibernate mit der gleichen Anzahl an Nachkommastellen annotiert. In Oracle selber sind die jeweiligen Spalten mit “NUMBER” als Datantyp gesetzt.

Irgendwo zwischen Speichern in die DB und Auslesen aus der DB werden die füllenden 0 nach dem Komma entfernt.

Ich greif jetzt bei der Implementierung der equals-Methode auf die compareTo-Methode von BigDecimal zurück. Das Ergebnis ist das gewünschte.

Man vergleicht Dezimalzahlen nicht auf Gleichheit, das geht immer irgendwann schief…

statt a=b immer Betrag(a-b)<Fehlertoleranz

@Bleiglanz

Es geht hier aber um BigDecimals, die sind intern über BigInteger realisiert. Double, float etc. sollten mit [Double|Float].compare(a,b) verglichen werden da dort -0.0, 0.0, NaN usw. korrekt behandelt werden.

[QUOTE=Bleiglanz]Man vergleicht Dezimalzahlen nicht auf Gleichheit, das geht immer irgendwann schief…

statt a=b immer Betrag(a-b)<Fehlertoleranz[/QUOTE]

Das Problem hierbei ist, der Auftraggeber ist sich noch nicht einig geworden ob es eine Fehlertoleranz geben soll und wenn ja, wie groß die Fehlertoleranz sein soll. Bis dahin sollen wir von exakter Übereinstimmung ausgehen.

Ja, schon klar.

Würde ich aber trotzdem nicht machen: sobald irgendwann mal irgendwo mit den Zahlen gerechnet wird, muss equals scheitern.

Ich kann mir überhaupt keinen Anwendungsfall vorstellen (außer vielleicht in der Finanzmathematik), wo man BigDecimal.equals jemals einsetzen könnte.

hier im Thread hast du ihn doch, oder allgemein in allen Fällen wo Ergebnisse verglichen werden sollen, Testfälle von Code etwa,
exakt wiederholte Berechnungen sollten bereits mit double jedes Mal übereinstimmen,


und gerade mit BigDecimal (und nicht Rechnungen mit endlosen Zahlen) sollte es doch nun wirklich genau gehen:

        double k = 45 / 1000000.0;
        System.out.println(k); // 4.5E-5
        k *= 10;
        System.out.println(k); // 4.5000000000000004E-4
        System.out.println("----");
        BigDecimal b = new BigDecimal(45).divide(new BigDecimal(1000000));
        System.out.println(b); // 0.000045
        b = b.multiply(BigDecimal.TEN);
        System.out.println(b); // 0.000450

Im Normalfall ja, wenn es nicht geht (eine Zahl nicht exakt darstellbar ist) fliegt ja eine Ausnahme wenn ich mich richtig erinnere. WENN alle Berechnungen mit BigDecimal durchgeführt werden (also nie zu double oder float übergegangen wird) dann ist tatsächlich alles in Butter. Aber das mit der DB? Könnte mir schon vorstellen, dass BigDecimal → Oracle NUMBER-TYP (hat nur 38 Stellen) → BigDeximal mal nicht equal ist.

Wie wird das denn in der Finanzmathematik gemacht, fast so genau wie möglich rechnen und den Endbetrag runden? Damit weiterrechnen oder mit dem genauen Betrag? Bin hal kein Bwl’er.

Edit: Es darf hal dort nicht stehen, einmal 4,10 und einmal 4,11.

[QUOTE=CyborgBeta]Wie wird das denn in der Finanzmathematik gemacht, fast so genau wie möglich rechnen und den Endbetrag runden? Damit weiterrechnen oder mit dem genauen Betrag? Bin hal kein Bwl’er.

Edit: Es darf hal dort nicht stehen, einmal 4,10 und einmal 4,11.[/QUOTE]

Wenn nur addiert, subtrahiert oder multipliziert wird, rechnet man ganzzahlig in der kleinsten Einheit (also z.B. Cent). Wenn das nicht geht nimmt man einen Typ wie BigDecimal, der eine “einstellbare” Genauigkeit und (meist gesetzlich vorgeschriebene) Rundungsregeln unterstützt. Was auf keinen Fall geht sind Fließkommatypen.

Ja, aber, mit ganzzahligen rechnen, aber es gibt doch auch Bereiche, da sind Cent und Mikrocent zu ungenau und uA beim teilen 0.5 auf-/abrunden - so etwas ähnliches meinte ich.
Edit: Gut, das ist offtopic und hat nur tageniell mit BigDeci zu tun.

@Landei

Weshalb keine Fließkommazahlen? Es gibt reichlich Anwendungen die korrekte Ergebnisse liefern unter Verwendung dieser. Die Fehler welche auftreten können sind ja in IEEE 754 beschrieben, die sind ja nicht magisch. Und oft steht auch eher die Frage im Raum ist der Fehler durch die Rechnung entstanden oder durch die Umwandlung von interner in externe Darstellung als String?

Geht man mit BigDecimal falsch um kann es einem nämlich genauso ergehen [1]. Des Weiteren ist es auch eine Frage der Anforderungen, hast du schon mal halbe Cents auf deinem Konto gesehen? Gerundet werden muss immer, auch bei der Verwendung von BigDecimal, es ist ein Unterschied ob es eine “mathematische” Zahl ist oder sie in irgendeinem Kontext, wie z.B. einem Geldbetrag.

Interessant sind die Beiträge von Peter Lawrey, oder z.B. der http://java.dzone.com/articles/working-money-java. Ein ganz netter Blog mit ein paar interessanten Einträgen zu Gleitkommazahlen ist hier http://randomascii.wordpress.com/category/floating-point/. Und mittlerweile gibt es auch einen JSR zum Thema Geld, zu finden hier https://java.net/projects/javamoney/pages/Home, den bisherigen geistigen Erguss kann man sich auch schon auf GitHub ansehen.

[1] new BigDecimal(0.1) anstelle von BigDecimal.valueOf(0.1), wobei letzteres noch die zahl so frisiert das tatsächlich 0.1 als String rauskommt, welcher dann in ein “vernünftiges” BD-Objekt gewandelt wird.

wenn du meinst sie beherrschen zu können dann gut für dich,
ich hatte weiter oben in #9 bisher ein eher krummes Beispiel, folgendes sehe ich aber als wichtig und nicht besonders praktikabel an:

        double d2 = 0.17;
        double sum = d1 + d2;
        System.out.println(sum); // 2.9699999999999998 ??

das kann es ja nun wirklich nicht sein

Umwandlungen zu String oder Konstruktor-Fehler oder das equals hier bei BigDecimal sind für sich Probleme, durchaus bedeutende,
bei Eigenimplementierung oder Wegkapselung sicher so nicht nochmal,
aber da gibt es ja nix gegeneinander aufzurechnen, mit BigDecimal ist bei einmaliger korrekt erlernter Bedienung (oder Wegkapselung) möglich, mit double schlicht nicht,
außer Dinge wie statt a=b immer Betrag(a-b)<Fehlertoleranz wären ok,

gerundet werden muss bei Finanzen eben nicht immer, Addition von zwei Geldbeträgen wie 2.8 und 0.17 sollte fehlerfrei funktionieren,
mit long ist man auf der sicheren Seite, Wertebereich Centgenau auch zumindest gleichhoch anscheinend

(unwichtig) Vertrauen kann vielleicht höher sein, auch der letzte Long-Wert vor dem Maximum wird mit +1 sicher funktionieren, da zweifelt keiner,
ob dagegen 1.45434533*10^15 wirklich noch 1 oder gar 0,01 korrekt dazuaddiert oder verschluckt? :wink:


freilich könnte man auch mit double ein korrektes Rechenwerk neu aufbauen, hinter jedem Plus auf zwei Stellen runden usw., da wird der Unterschied geringer

Brian Goetz dazu: http://www.ibm.com/developerworks/library/j-jtp0114/

Die Berechnung ist exakt. Das Problem bei equals ist, dass dort auch noch die Genauigkeit Berücksichtigung findet, wie es ja auch in #1 schon erwähnt wurde. Die Zahlen sind deshalb unterschiedlich, wenn eine andere Anzahl an Nachkommastellen existiert (also bspw. 1.0 != 1.00).

Ist das tatsächlich so? Es kommen da dann doch deutlich abweichende Ergebnisse zustande. Zum Beispiel bei der Berechnung von Mehrwertsteuer bei einer Rechnung über mehrere Positionen. Wenn man die USt pro Position berechnet und ausweist und dann die Beträge summiert, bekommt man ein anderes (falsches) Ergebnis, als wenn man die Summe der Beträge bildet und dann die USt berechnet.
Daher ging ich auch davon aus, dass man möglichst mit maximaler Genauigkeit rechnet und erst am Ende bei der Gesamtsumme rundet.

Ich meinte natürlich ganzzahlige Multiplikation, bei MwSt und Co. braucht man BigDecimal.

Ok, klar. Dann war das ein Verständnisfehler. Bei ganzzahligen Multiplikationen kann natürlich kein Nachkommaanteil entstehen.

@Landei

Die Aussage ist mir zu pauschal.

Beispiel:

Das Snippet erzeugt bei mir folgende Werte

975.6994
975.6994
975.6994000000002
975.6994000000001

Dabei wurde hier nicht einmal eine kompensierte Summe (so wie es für doubles/float korrekt wäre) oder ähnliches verwendet. Aber die Werte sind als Ergebnis alle nicht richtig. Weshalb nicht? Weil im Kontext des Einkaufens Preise höchstens 2 Nachkommastellen haben. Würden die Ergebnisse korrekt gerundet gäbe es keinen Unterschied.

public class Test {
    private static double round(double d) {
        return ((long) (d < 0 ? d * 100 - 0.5 : d * 100 + 0.5)) / 100.0;
    }

    public static void main(String ... args){
        double mwst = 0.19;
        BigDecimal mwBig = BigDecimal.valueOf(0.19);

        double lo = 0.0;
        double hi = 100.0;

        double[] values =
                new Random(42)
                    .doubles(100)
                    .map(d -> d * ((hi - lo) + lo))
                    .map(Test::round)
                    .toArray();

        System.out.println(
            Arrays.stream(values)
                .mapToObj(BigDecimal::valueOf)
                .map(b -> b.multiply(mwBig))
                .reduce(BigDecimal.ZERO, BigDecimal::add));

        System.out.println(
            Arrays.stream(values)
                .mapToObj(BigDecimal::valueOf)
                .reduce(BigDecimal.ZERO, BigDecimal::add)
                .multiply(mwBig));

        System.out.println(
            Arrays.stream(values)
                .map(d -> d * mwst).reduce(0.0, (a, b) -> a + b));

        System.out.println(
                Arrays.stream(values)
                    .reduce(0.0, (a, b) -> a + b) * mwst
                );
    }
}