Dienstag, 27. Februar 2018

Rechnen mit Java in der Finanzwelt (German)

Nach vielen Jahren in der IT Crowd wurde es Zeit für einen eigenen Blog.
Was mir als IT Consultant so alles an nützlichen und skurrilen Dingen über den Weg läuft, halte ich hier fest.

In unzähligen Fällen, in denen Firmen Geld verloren ging oder zuviel auftauchte, stellte ich fest - das muss nicht immer mit den Panzerknackern zu tun haben. Programmiersprachen erledigen das ganz von selbst - gesetzt des Falles, dass sie nicht richtig angewandt werden.

Im nachfolgenden Beispiel, stelle ich dar, wie einem realen Unternehmen Fehlrechnungen unterliefen, die sie sich zunächst nicht erklären konnten.

Natürlich werden weder Namen, noch Zeit und Ort genannt. Der Fall könnte gestern passiert sein oder vor 5 Jahren - das soll keine Rolle spielen. Geschäftsdetails sind nicht enthalten und ich habe den Fall mit einem privaten Beispiel nachgestellt.

Viel Spaß beim Lesen.

P.S.
Solche Sachen werden gelegentlich verfilmt. Wer mal sehen will, wie ich und meinesgleichen so leben und was sie alles erdulden müssen, sollte sich diesen Film ansehen:

Alles Routine (DE)


Rechnen mit Java in der Finanzwelt

von: Tim Harder
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 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.

How To Resolve GIT Merge Conflicts with IntelliJ

Resolve a Merge Conflict in GIT with IntelliJ Let us assume you are using Bitbucket or a similar tool to administrate and host your GI...