Rechnen mit Java in der Finanzwelt

Immer wieder stellt man bei der Sichtung von Software fest, dass das Rechnen in Java zu Problemen führt, wenn primitive Datentypen verwendet werden.

Hier ein aktuelles Beispiel eines Rundungsfehlers, bei einem Finanz Dienstleister, der beliebig hohe Summen zuviel oder zuwenig berechnet, ohne, dass es gleich auffällt:


// GIVEN

final Integer price = -595;

final Integer someUnit = 90;

{

// WHEN

int multiplicationResult = price * someUnit;

// = -53550

Integer resultAmount = multiplicationResult / 100;

// = -535

// …Division fehlgeschlagen,

// Nachkommastellen ignoriert...

// THEN

assertTrue(resultAmount == -535);

// Falsch. Es müsste -536 sein.

}

…und was passiert, wenn man mit Datentypen rechnet, die Zahlenräume korrekt abbilden können:

 


// GIVEN

final Integer price = -595;

final Integer someUnit = 90;

{

// WHEN

BigDecimal priceBD = new BigDecimal(price);

BigDecimal someUnitBD = new BigDecimal(someUnit);

BigDecimal hundred = new BigDecimal(100);

BigDecimal multiplicationResult = priceBD.multiply(someUnitBD); // = -53550

// = -535.5 bedeutet, alles sieht gut aus

BigDecimal resultAmountBD = multiplicationResult.divide(hundred);

final int noDecimalPlaces = 0;

// rounding mode essentiell für das Ergebnis

final RoundingMode roundingMode = RoundingMode.HALF_UP;

// Nachkommastellen definieren

BigDecimal result = resultAmountBD.setScale(noDecimalPlaces, roundingMode);

// THEN

assertThat(result.toBigInteger().intValue(), equalTo(-536)); // Richtig!

}


 

Warum ist das so?

Laut Standard IEEE 754 wird für Exception Handling und Zahlenräume definiert, wie float und double rechnen.
Floating Points sind dafür geeignet, Annäherungen zu beschreiben, schnell, aber ohne die für ein exaktes Resultat notwendige Präzision.
BigDecimal arbeitet intern mit String Repräsentationen und arbeitet sich Stück für Stück vor, weshalb dieser Datentyp zum einen unabhängiger von definierten, sprich begrenzten Zahlenräumen ist.
Außerdem kann die Präzision der Berechnung genauestens definiert werden.
Das ermöglicht die Erstellung von Kontrakten zum Thema Berechnung: Wird aufgerundet, oder abgerundet? Wieviele Nachkommastellen? Welche Darstellung im System?
So haben Banken in den meisten Fällen den „Bank Rounding Mode“. Alle Systeme, die miteinander im Kontext eines Anwendungsfalles sprechen, einigen sich auf diese Form der Berechnung.
 
Warum ist das so wichtig?
In einem aktuellen Fall nimmt ein System innerhalb eines Kontexts nur zwei Nachkommastellen an.
Es berechnet daraus Summen und schickt diese zurück. Dadurch entsteht eine Differenz zwischen den Eingangswerten und darauf basierenden Annahmen und den zusätzlichen Ergebnissen aus diesem System. Das System ist eine Blackbox und es kann nicht mehr festgestellt werden, was genau dort berechnet wurde. Hätte man von vornherein einen Kontrakt definiert, der nicht nur die Präzision der Nachkommastellen, sondern auch die Rundungs Modi definiert, rechneten alle Systeme gleich.
So etwas führt mitunter zu beliebig hohen Summen, die akkumuliert zuviel oder zuwenig berechnet werden und welche nachher buchhalterisch zu Verlusten führen, da man mit Geld hantiert, welches es genau genommen gar nicht gibt.