Artikelformat

MySQL und Währungsbeträge – FLOAT vs. DECIMAL

Bei einem Projekt an dem ich gearbeitet habe, wurden Euro-Beträge in einer Datenbank gespeichert und mit diesen dann Berechnungen durchgeführt. Normalerweise würde man denken “So schwer kann das ja nicht sein” aber dummerweise gibt es da einen kleinen Fallstrick den vielleicht noch nicht jeder kennt.

Denkt man darüber nach Beträge mit Nachkommastellen in einer Datenbank zu speichern stolpert man schnell über die üblichen Datentypen wie FLOAT, DOUBLE oder DECIMAL. Und wenn man unbedarft an die Sache herangeht, sieht das auch erst mal vollkommen problemlos aus. Nehmen wir mal die folgende kleine Test-Datenbank:

CREATE TABLE `test` (
`test_float` float(10,2) NOT NULL,
`test_decimal` decimal(10,2) NOT NULL
);

In diese fügen wir dann zwei identische Werte ein:

INSERT INTO `test` (`test_float`, `test_decimal`) VALUES (5.43, 5.43);

Schaut man sich die Datenbank nun z.B. in phpMyAdmin an, sieht alles genau so aus, wie man es erwartet. In beiden Feldern scheint der Wert “5,43” zu stehen. Ihr merkt schon: das entscheidende Wort ist hier “scheint”.

Quizfrage: welches Ergebnis erwartet ihr nach Ausführung von

SELECT (test_float * 1.0000000) AS f, (test_decimal * 1.0000000) AS d FROM test

(das 1.0000000 führt dazu, dass MySQL mehr Nachkommastellen anzeigt) ? Wer mit f = 5.4299998 und d = 5.430000000 geantwortet hat liegt richtig.

Eigentlich erklärt schon das MySQL-Handbuch warum das passiert:

Die Datentypen FLOAT und DOUBLE werden zur Darstellung annähernder numerischer Datenwerte verwendet. Für FLOAT gestattet der SQL-Standard optional die Angabe der Genauigkeit (nicht aber des Bereichs des Exponenten) in Bits, die dem Schlüsselwort FLOAT in Klammern folgen.

und

Die Datentypen DECIMAL und NUMERIC werden zur Speicherung exakter numerischer Datenwerte verwendet.

FLOATs sind also keine exakte Repräsentation eines Wertes sondern lediglich eine Annäherung daran. Werden dann z.B. nur 2 Nachkommastellen angezeigt erscheint der Wert so wie man es erwartet. Bei Berechnungen benutzt MySQL aber intern den “echten” Wert der leicht von dem erwarteten Wert abweicht. Je mehr Daten nun in eine Berechnung einfließen, desto stärker wirken sich diese Abweichungen aus. Bei dem oben angesprochenen Projekt waren das z.B. letztlich 4 Cent die gefehlt haben (bei knapp 20000 Datensätzen die in die Berechnung eingeflossen sind).

Das mag z.B. noch akzeptabel sein wenn man in einer Website die durchschnittlichen Werbeeinnahmen der letzten 12 Monate als Information anzeigen möchte. Nutzt man diese Werte aber für Rechnungen, etc. dann wird es kritisch. Man sollte also in diesen Fällen auf DECIMAL setzen.

Autor: Christian

Baujahr 1976, Software-Entwickler und Web-Designer aus Leidenschaft, Erfahrung in gefühlten 9342049 Programmiersprachen (PHP, Perl, C#, VB.Net, VB6, Delphi um nur einige zu nennen), in der Vergangenheit an diversen Open Source Projekten beteiligt (allen voran das gute alte YaBB und YaBB SE)

6 Kommentare

  1. Hallo Christian.

    Sehr interessanter Artikel. Könnte für mein nächstes Projekt sehr nützlich sein. (Wenns kommt)

    • Allerdings! Habe schon einmal mit einem solchen Problem gekämpft… war kein Spaß ;) Man sollte doch immer brav die Dokumentation durchlesen!

  2. Da hab ich ja instinktiv alles richtig gemacht. Ich nutze immer decimal.
    Aber wofür ist es gut/sinnvoll ungenaue Werte zu speichern? Wozu brauche ich dann float? Hab ich noch nie verwendet oder drüber nachgedacht.

  3. Ich schreibe Geldbeträge sogar “nur” als String in die Datenbank, also varchar. Da ich in php sowieso mit bcadd usw. arbeite, ist die Nachbehandlung gar nicht sooo umfangreich. Naja, und prüfen sollte man jeden Wert auch aus einer Datenbank vor der weiteren Verarbeitung sowieso.
    Berechnungen auf mysql-Basis kann ich jedoch auf diese Art nicht nutzen.

  4. Ähnlich ärgerlich finde ich rechnen mit Datumswerten, insbesondere Zeiträume, wenn dort gerade mal wieder die Uhr verstellt wird ;)!

    Ansonsten: pfui, varchar, das gibt viel zu viel dynamische Tabellen. Es lebe der INDEX!

  5. Besten Dank für diesen Beitrag. Sehr verständlich geschrieben.
    Damit werde ich mir ein paar Nerven sparen können.