Wie gehe ich mit Geldwerten in PHP und MySQL um?

16

Ich habe einen riesigen Haufen älteren Codes geerbt, der in PHP auf einer MySQL-Datenbank geschrieben wurde. Mir ist aufgefallen, dass die Anwendung doublesDaten speichert und bearbeitet.

Nun bin ich auf zahlreiche Beiträge gestoßen, in denen erwähnt wurde, dass sie doubleaufgrund von Rundungsfehlern nicht für Geldgeschäfte geeignet sind. Ich habe jedoch noch keine vollständige Lösung gefunden, wie monetäre Werte in PHP-Code behandelt und in einer MySQL-Datenbank gespeichert werden sollen.

Gibt es eine bewährte Methode für den Umgang mit Geld speziell in PHP?

Dinge, die ich suche, sind:

  1. Wie sollen die Daten in der Datenbank gespeichert werden? Spaltentyp? Größe?
  2. Wie sollen die Daten bei normaler Addition, Subtraktion behandelt werden. Multiplikation oder Division?
  3. Wann sollte ich die Werte runden? Wie viel Rundung ist akzeptabel, wenn überhaupt?
  4. Gibt es einen Unterschied zwischen dem Umgang mit hohen und niedrigen Geldwerten?

Hinweis: Ein SEHR vereinfachter Beispielcode, wie ich im Alltag auf Geldwerte stoßen könnte (verschiedene Sicherheitsbedenken wurden zur Vereinfachung ignoriert. Natürlich würde ich meinen Code im wirklichen Leben niemals so verwenden):

$a= $_POST['price_in_dollars']; //-->(ex: 25.06) will be read as a string should it be cast to double?
$b= $_POST['discount_rate'];//-->(ex: 0.35) value will always be less than 1
$valueToBeStored= $a * $b; //--> any hint here is welcomed 

$valueFromDatabase= $row['price']; //--> price column in database could be double, decimal,...etc.

$priceToPrint=$valueFromDatabase * 0.25; //again cast needed or not?

Ich hoffe, dass Sie diesen Beispielcode verwenden, um mehr Anwendungsfälle herauszubringen und ihn nicht wörtlich zu nehmen.

Bonus - Frage Wenn ich ein ORM wie Lehre oder PROPEL zu verwenden, wie anders wird es Geld in meinem Code zu verwenden sein.

Songo
quelle
1
Ich weiß PHP nicht aber Terminologie zu wissen , ist von unschätzbarem Wert in diesen Szenarien für Google-Fu, der Begriff Sie suchen ist „beliebige Genauigkeit“ , auf die reagiert mit Google php.net/manual/en/book.bc.php
Jimmy Hoffa
1
@ Jimmy Hoffa: willkürliche Präzision ist im Allgemeinen nicht das, was man braucht, etwas, das Dezimalbrüche genau darstellen und damit arbeiten kann. Natürlich finden Sie häufig beide kombiniert, aber zB der decimalTyp in C # hat eine begrenzte Genauigkeit, ist aber perfekt für Geldwerte geeignet.
Michael Borgwardt
1
@MichaelBorgwardt richtig, ich vergesse rationale Typen, weil so viele Sprachen sie nicht haben. Guter Anruf.
Jimmy Hoffa
Nachdem ich viel mit Währungen und altem Code gearbeitet habe, würde ich sagen, ich speichere ihn in ganzen Zahlen und verwende Cent anstelle von Dollar. Beachten Sie jedoch, dass 32 Ganzzahlen eine überraschend kleine Menge enthalten können. 4) Eine 32-Bit-Ganzzahl kann nur eine Menge von 21 Millionen enthalten, wenn sie ohne Vorzeichen ist und Cent verwendet.
Pieter B
Abgesehen davon erschreckt mich Ihr Beispielcode, der die POST-Preise und -Rabatte anzeigt. Hoffentlich ist es so vereinfacht, dass es nicht die tatsächliche Verwendung widerspiegelt, aber es sieht so aus, als würden Sie dem Browser vertrauen, wenn er Ihnen den Preis eines Artikels durch Zurückschicken eines verborgenen Feldes oder dergleichen mitteilt.
Carson63000

Antworten:

6

Es kann sehr schwierig sein, mit PHP / MySQL mit Zahlen umzugehen. Wenn Sie eine Dezimalzahl (10,2) verwenden und Ihre Zahl länger ist oder eine höhere Genauigkeit aufweist, wird sie ohne Fehler abgeschnitten (es sei denn, Sie haben den richtigen Modus für Ihren Datenbankserver festgelegt).

Um große Werte oder hochpräzise Werte zu verarbeiten, können Sie eine Bibliothek wie BCMath verwenden , mit der Sie grundlegende Operationen für große Zahlen ausführen und die erforderliche Präzision beibehalten können.

Ich bin mir nicht sicher, welche genauen Berechnungen Sie durchführen werden, aber Sie müssen auch bedenken, dass (0,22 * 0,4576) + (0,78 * 0,4576) nicht gleich 0,4576 ist, wenn Sie nicht die richtige Präzision während des Prozesses verwenden.

Die maximale Größe von DECIMAL in MySQL beträgt 65, es sollte also für jeden Zweck mehr als genug sein. Wenn Sie den Feldtyp DECIMAL verwenden, wird dieser unabhängig von der Verwendung eines ORM oder eines einfachen PDO / mysql (i) als String zurückgegeben.

Wie sollen die Daten in der Datenbank gespeichert werden? Spaltentyp? Größe?

DECIMAL mit der Präzision, die Sie brauchen. Wenn Sie Wechselkurse verwenden, benötigen Sie mindestens vier Dezimalstellen

Wie sollen die Daten bei normaler Addition, Subtraktion behandelt werden. Multiplikation oder Division?

Verwenden Sie BCMath, um auf der sicheren Seite zu sein, und warum die Verwendung von float möglicherweise keine gute Idee ist

Wann sollte ich die Werte runden? Wie viel Rundung ist akzeptabel, wenn überhaupt?

Für Geldwerte sind normalerweise zwei Dezimalstellen zulässig, aber Sie benötigen möglicherweise mehr, wenn Sie beispielsweise Wechselkurse verwenden.

Gibt es einen Unterschied zwischen dem Umgang mit hohen und niedrigen Geldwerten?

Kommt drauf an, was du meinst. Es gibt definitiv einen Unterschied zwischen der Handhabung von Zahlen mit hoher Präzision.

onlineapplab.com
quelle
9

Eine einfache Lösung besteht darin, sie als ganze Zahlen zu speichern. 99,99 gespeichert als 9999. Wenn dies nicht funktioniert (und es gibt viele Gründe, warum dies eine schlechte Wahl sein könnte), können Sie den Typ Dezimal verwenden. http://dev.mysql.com/doc/refman/5.0/de/precision-math-decimal-changes.html auf der MySQL-Seite. Auf der PHP-Seite habe ich diese /programming/3244094/decimal-type-in-php gefunden, die genau das sein könnte, wonach Sie suchen.

Bonusfrage: Schwer zu sagen. Der Orm wird basierend auf den ausgewählten Datentypen funktionieren. Ich würde sagen, dass Sie einige Dinge mit Abstraktion tun könnten, um zu helfen, aber dieses spezielle Problem wird nicht einfach durch den Wechsel zu einem ORM gelöst.

Ominus
quelle
1
FWIW, Drupal Commerce verwendet den 9999-Trick, um seine Preise zu speichern.
Florian Margaine
1
"und es gibt viele Gründe, warum dies eine schlechte Wahl sein könnte" Würden Sie bitte einige der Probleme dieser Praxis mitteilen?
Songo
2
@Songo: siehe floating-point-gui.de/formats/integer
Michael Borgwardt
@ MichaelBorgwardt Vielen Dank für die Info, Songo Entschuldigung für die Verzögerung, Hurrikan hat uns
rausgenommen
3

Ich werde versuchen, meine Erfahrung in diese zu stecken:

Dinge, die ich suche, sind:

Wie sollen die Daten in der Datenbank gespeichert werden? Spaltentyp? Größe?

Ich habe DECIMAL(10,2)für MySQL ohne Probleme verwendet (8 Abschlüsse und 2 Dezimalstellen == 99,999,999,99 == große Menge), aber dies hängt von der Geldmenge ab, die Sie abdecken müssen. Bei großen Mengen ist besondere Vorsicht geboten (z. B. maximale OS-Schwebewerte). Im Dezimalteil verwende ich 2 Werte, um das Abschneiden oder Runden von Werten zu vermeiden. Es gibt nur wenige Fälle, in denen Sie mehr Dezimalzahlen benötigen (in diesem Fall müssen Sie sicherstellen, dass der Benutzer mit allen Daten arbeitet, da sonst die Daten unbrauchbar werden).

Wie sollen die Daten bei normaler Addition, Subtraktion behandelt werden. Multiplikation oder Division?

Arbeiten Sie mit einer Währung und einer Umrechnungstabelle (mit Daten). Auf diese Weise stellen Sie sicher, dass immer der richtige Betrag gespeichert wird. Ein Extra: Speichern Sie komplette Werte und erstellen Sie eine Ansicht mit Berechnungsergebnissen. Auf diese Weise können Sie schnell Werte festlegen

Wann sollte ich die Werte runden? Wie viel Rundung ist akzeptabel, wenn überhaupt?

Auch dies hängt von der Geldmenge Ihres Systems ab. Denken Sie immer in KISS-Begriffen, es sei denn, Sie müssen in das Chaos der Wechselstube geraten

Gibt es einen Unterschied zwischen dem Umgang mit hohen und niedrigen Geldwerten?

Abhängig von Ihrem Betriebssystem und Ihren Programmiersprachen müssen Sie immer Ihre Maximal- und Minimalwerte überprüfen

Alwin Kesler
quelle
Danke für die Antwort, aber wenn ich mit so etwas wie $valueToBeStored= $a * $b;if umgehen muss $aund $bbeide als Dezimalstellen aus der Datenbank gelesen werden, denke ich, dass sie doublein PHP umgewandelt werden, oder? Wird das die Zahlen beeinflussen?
Songo
$aund $bvon db genommen? In meinem Beispiel müssen Sie also niemals speichern, $valueToBeStoredda Sie immer die Quelle $aund die $bDaten haben. So können Sie den Wert in einer Funktion oder so programmgesteuert bearbeiten oder eine MySQL-Ansicht mit dem Spaltenergebnis erstellen. Auf diese Weise müssen Sie sich keine Sorgen machen, wenn ein Wert geändert werden muss (fehleranfällig)
Alwin Kesler,