Geld in einer Dezimalspalte speichern - welche Präzision und Skalierung?


173

Ich verwende eine Dezimalspalte, um Geldwerte in einer Datenbank zu speichern, und heute habe ich mich gefragt, welche Präzision und Skalierung ich verwenden soll.

Da angeblich char-Spalten mit fester Breite effizienter sind, dachte ich, dass dies auch für Dezimalspalten gelten könnte. Ist es?

Und welche Präzision und Skalierung sollte ich verwenden? Ich dachte rund um die Uhr an Präzision. Ist das übertrieben, nicht genug oder in Ordnung?


Folgendes habe ich beschlossen:

  • Speichern Sie die Conversion-Raten (falls zutreffend) in der Transaktionstabelle selbst als Float
  • Speichern Sie die Währung in der Kontotabelle
  • Der Transaktionsbetrag beträgt a DECIMAL(19,4)
  • Alle Berechnungen mit einer Conversion-Rate werden von meiner Anwendung verarbeitet, sodass ich die Kontrolle über Rundungsprobleme behalten kann

Ich denke nicht, dass ein Float für die Conversion-Rate ein Problem ist, da es hauptsächlich als Referenz dient, und ich werde es trotzdem auf eine Dezimalstelle umwandeln.

Vielen Dank für Ihre wertvollen Beiträge.


2
Stellen Sie sich die Frage: Ist es wirklich notwendig, die Daten in Dezimalform zu speichern? Kann ich die Daten nicht als Cents / Pennies -> Ganzzahlen speichern?
Terence

5
DECIMAL(19, 4) Dies ist eine beliebte Wahl. Überprüfen Sie dies auch hier. Weltwährungsformate, um zu entscheiden, wie viele Dezimalstellen verwendet werden sollen. Hoffnung hilft.
Shaijut

Antworten:


181

Wenn Sie auf der Suche nach einer Einheitsgröße sind, würde ich vorschlagen, dass dies DECIMAL(19, 4)eine beliebte Wahl ist (ein schnelles Google bestätigt dies). Ich denke, dies stammt aus dem alten Datentyp VBA / Access / Jet Currency, der der erste Festkomma-Dezimaltyp in der Sprache ist. Decimalwurde nur in VB6 / VBA6 / Jet 4.0 im Stil der Version 1.0 (dh nicht vollständig implementiert) geliefert.

Als Faustregel für die Speicherung von Festkomma-Dezimalwerten gilt, dass mindestens eine Dezimalstelle mehr gespeichert wird, als Sie tatsächlich benötigen, um das Runden zu ermöglichen. Einer der Gründe für die Zuordnung des alten CurrencyTyps im vorderen Ende zum DECIMAL(19, 4)Typ im hinteren Ende war, dass die CurrencyBanker von Natur aus DECIMAL(p, s)gerundet waren , während sie durch Abschneiden gerundet waren.

Eine zusätzliche Dezimalstelle im Speicher für DECIMALermöglicht die Implementierung eines benutzerdefinierten Rundungsalgorithmus, anstatt die Standardeinstellung des Anbieters zu übernehmen (und die Rundung der Banker ist, gelinde gesagt, alarmierend, wenn ein Designer erwartet, dass alle Werte, die mit 0,5 enden, von Null abrunden). .

Ja, DECIMAL(24, 8)klingt für mich nach Overkill. Die meisten Währungen werden mit vier oder fünf Dezimalstellen angegeben. Ich kenne Situationen, in denen eine Dezimalskala von 8 (oder mehr) erforderlich ist , aber hier wurde ein „normaler“ Geldbetrag (z. B. vier Dezimalstellen) anteilig festgelegt, was bedeutet, dass die Dezimalgenauigkeit entsprechend reduziert werden sollte (auch berücksichtigen) ein Gleitkommatyp unter solchen Umständen). Und heutzutage hat niemand mehr so ​​viel Geld, um eine Dezimalgenauigkeit von 24 zu verlangen :)

Anstelle eines einheitlichen Ansatzes sind jedoch möglicherweise einige Untersuchungen angebracht. Fragen Sie Ihren Designer oder Domain-Experten nach den möglicherweise geltenden Rechnungslegungsvorschriften: GAAP, EU usw. Ich erinnere mich vage an einige innerstaatliche EU-Überweisungen mit expliziten Regeln für das Runden auf fünf Dezimalstellen, die daher DECIMAL(p, 6)zur Speicherung verwendet werden. Buchhalter scheinen im Allgemeinen vier Dezimalstellen zu bevorzugen.


PS Vermeiden Sie den MONEYDatentyp von SQL Server, da er beim Runden schwerwiegende Probleme mit der Genauigkeit aufweist, unter anderem bei der Portabilität usw. Siehe Aaron Bertrands Blog .


Microsoft und Sprachdesigner haben sich für die Rundung von Bankern entschieden, weil Hardware-Designer sie gewählt haben [Zitat?]. Es ist beispielsweise in den Standards des Institute of Electrical and Electronics Engineers (IEEE) verankert. Und Hardware-Designer haben es gewählt, weil Mathematiker es bevorzugen. Siehe Wikipedia ; Um es zu paraphrasieren: Die Ausgabe von 1906 von Probability and Theory of Errors nannte dies "die Regel des Computers" ("Computer" bedeutet Menschen, die Berechnungen durchführen).


1
In dieser Antwort und auf dieser Seite erfahren Sie mehr darüber, warum Sprachdesigner die Rundung von Banker wählen.
Nick Chammas

1
onedaywhen: Die Rundung des Bankiers ist nicht auf Microsoft zurückzuführen. @ NickChammas: Es ist auch keine Erfindung eines Sprachdesigners. Microsoft und Sprachdesigner haben es grundsätzlich gewählt, weil Hardware-Designer es gewählt haben. Es ist beispielsweise in den Standards des Institute of Electrical and Electronics Engineers (IEEE) verankert. Und Hardware-Designer haben es gewählt, weil Mathematiker es bevorzugen. Siehe en.wikipedia.org/wiki/Rounding#History ; um es zu paraphrasieren: Die Ausgabe von 1906 von Probability and Theory of Errors nannte dies "die Regel des Computers" ("Computer" bedeutet Menschen, die Berechnungen durchführen).
Phoog

9
Was soll ich für Bitcoin verwenden?
Toolkit

1
@onedaywhen warum ist DECIMAL(19, 4)beliebter als DECIMAL(19, 2)? Die meisten Weltwährungen sind nur zwei Dezimalstellen.
Kokedude

3
@ zypA13510: Ja, meine Behauptung ist so vor zehn Jahren! Aber dies ist meine dritthäufigste Antwort und macht eine ganze
Menge

105

Wir haben kürzlich ein System implementiert, das Werte in mehreren Währungen verarbeiten und zwischen ihnen umrechnen muss, und einige Dinge auf die harte Tour herausgefunden.

NIEMALS SCHWIMMENDE PUNKTNUMMERN FÜR GELD VERWENDEN

Gleitkomma-Arithmetik führt zu Ungenauigkeiten, die möglicherweise erst bemerkt werden, wenn sie etwas vermasselt haben. Alle Werte sollten entweder als Ganzzahlen oder als Typen mit festen Dezimalstellen gespeichert werden. Wenn Sie sich für die Verwendung eines Typs mit festen Dezimalstellen entscheiden, stellen Sie sicher, dass Sie genau verstehen, was dieser Typ unter der Haube tut (dh intern eine Ganzzahl oder einen Gleitkomma verwendet Art).

Wenn Sie Berechnungen oder Konvertierungen durchführen müssen:

  1. Konvertieren Sie Werte in Gleitkommawerte
  2. Neuen Wert berechnen
  3. Runden Sie die Zahl und konvertieren Sie sie zurück in eine Ganzzahl

Wenn Sie eine Gleitkommazahl in Schritt 3 wieder in eine Ganzzahl konvertieren, wandeln Sie sie nicht einfach um - verwenden Sie eine mathematische Funktion, um sie zuerst zu runden. Dies wird normalerweise sein round, obwohl es in besonderen Fällen sein könnte flooroder ceil. Kennen Sie den Unterschied und wählen Sie sorgfältig.

Speichern Sie den Typ einer Zahl neben dem Wert

Dies ist für Sie möglicherweise nicht so wichtig, wenn Sie nur eine Währung abwickeln, aber für uns war es wichtig, mehrere Währungen abzuwickeln. Wir haben den dreistelligen Code für eine Währung wie USD, GBP, JPY, EUR usw. verwendet.

Je nach Situation kann es auch hilfreich sein, Folgendes zu speichern:

  • Ob die Nummer vor oder nach Steuern ist (und wie hoch der Steuersatz war)
  • Ob die Zahl das Ergebnis einer Konvertierung ist (und woraus sie konvertiert wurde)

Kennen Sie die Genauigkeitsgrenzen der Zahlen, mit denen Sie es zu tun haben

Für reale Werte möchten Sie so genau sein wie die kleinste Einheit der Währung. Dies bedeutet, dass Sie keine Werte haben, die kleiner als ein Cent, ein Penny, ein Yen, ein Fen usw. sind. Speichern Sie Werte nicht ohne Grund mit höherer Genauigkeit.

Intern können Sie sich für kleinere Werte entscheiden. In diesem Fall handelt es sich um eine andere Art von Währungswert . Stellen Sie sicher, dass Ihr Code weiß, welcher welcher ist, und dass er sie nicht verwechselt. Vermeiden Sie auch hier die Verwendung von Gleitkommawerten.


Wenn wir all diese Regeln zusammenfassen, haben wir uns für die folgenden Regeln entschieden. Im laufenden Code werden Währungen mit einer Ganzzahl für die kleinste Einheit gespeichert.

class Currency {
   String code;       //  eg "USD"
   int value;         //  eg 2500
   boolean converted;
}

class Price {
   Currency grossValue;
   Currency netValue;
   Tax taxRate;
}

In der Datenbank werden die Werte als Zeichenfolge im folgenden Format gespeichert:

USD:2500

Das speichert den Wert von 25,00 $. Dies war nur möglich, weil sich der Code, der sich mit Währungen befasst, nicht in der Datenbankebene selbst befinden muss, sodass alle Werte zuerst in den Speicher konvertiert werden können. Andere Situationen eignen sich zweifellos für andere Lösungen.


Und falls ich es vorher nicht klar gemacht habe, benutze keinen Float!


1
Sag niemals nie: Manchmal sind Geldbeträge anteilig und müssen später erneut addiert werden. Beispiel: Division der Gesamtdividende (relativ gering) geteilt durch die Anzahl der ausgegebenen Aktien (relativ klein), um netto pro Aktie zu erhalten. Manchmal schweben Runden besser :)
Tag, wenn

23
Ich stehe zu meinem nie. Die Gleitkomma-Spezifikation weist Ungenauigkeiten auf, die sich summieren, je mehr Berechnungen Sie durchführen. Wenn Sie Werte speichern müssen, die kleiner als ein Cent oder ein Penny sind, definieren Sie die Genauigkeitsstufe, die Sie benötigen, und halten Sie sich daran. Verwenden Sie keinen Schwimmer. Ernsthaft. Es ist eine schlechte Idee.
Marcus Downing

4
Diese Antwort stimmt auch mit den von Douglas Crockford in seiner "Crockford on JavaScript-Reihe" beschriebenen Best Practices für Javascript überein, in denen er empfiehlt, alle Währungsberechnungen in PENNIES durchzuführen, um Maschinenfehler beim Runden zu vermeiden. Wenn Sie also mit Währungen in Javascript arbeiten, ist es sehr sinnvoll, den Wert auf diese Weise zu speichern.
Papierreduktion

1
@onedaywhen In diesen Netto-Fällen pro Aktie können Sie die anteiligen Beträge summieren und mit der ursprünglichen Summe vergleichen und eine Strategie für den Umgang mit dem Rest entwickeln (bei Verwendung von Dezimal- / Ganzzahltypen).
Shiv

2
Für MySQL empfehle ich, die Ganzzahl (2500) so zu speichern, als bigintob Sie nach dem Betrag sortieren möchten . Und verschwenden Sie nicht Ihre Zeit mit PHP 32bit, wenn Sie mit großen ganzen Zahlen arbeiten, aktualisieren Sie auf 64bit oder Node.JS;)
Ricky Boyce

4

Verwenden Sie beim Umgang mit Geld in MySQL DECIMAL (13,2), wenn Sie die Genauigkeit Ihrer Geldwerte kennen, oder DOUBLE, wenn Sie nur einen schnellen, ausreichend guten ungefähren Wert wünschen. Wenn Ihre Anwendung also Geldwerte bis zu einer Billion Dollar (oder Euro oder Pfund) verarbeiten muss, sollte dies funktionieren:

DECIMAL(13, 2)

Wenn Sie GAAP einhalten müssen, verwenden Sie:

DECIMAL(13, 4)

3
Können Sie anstelle des Inhalts eines 2500-seitigen Dokuments einen Link zu einem bestimmten Teil der GAAP-Richtlinien erstellen? Vielen Dank.
ReactingToAngularVues

@ReactingToAngularVues anscheinend hat sich die Seite geändert. Entschuldigung
pollux1er

2

Mit 4 Dezimalstellen können Sie die kleinsten Währungsuntereinheiten der Welt genau speichern. Sie können es weiter reduzieren, wenn Sie eine Genauigkeit der Mikrozahlung (Nanopayment?!) Benötigen.

Auch ich bevorzuge DECIMALDBMS-spezifische Geldarten. Sie können diese Logik sicherer in der IMO-Anwendung beibehalten. Ein anderer Ansatz in die gleiche Richtung besteht einfach darin, eine [lange] Ganzzahl zu verwenden, wobei die Formatierung in ¤unit.subunit für die menschliche Lesbarkeit (¤ = Währungssymbol) auf Anwendungsebene erfolgt.


1

Der Gelddatentyp in SQL Server hat vier Nachkommastellen.

Aus SQL Server 2000 Books Online:

Gelddaten repräsentieren positive oder negative Geldbeträge. In Microsoft® SQL Server ™ 2000 werden Gelddaten unter Verwendung der Datentypen Geld und Kleingeld gespeichert. Gelddaten können mit einer Genauigkeit von vier Dezimalstellen gespeichert werden. Verwenden Sie den Gelddatentyp, um Werte im Bereich von -922.337.203.685.477.5808 bis +922.337.203.685.477.5807 zu speichern (zum Speichern eines Werts sind 8 Byte erforderlich). Verwenden Sie den Datentyp smallmoney, um Werte im Bereich von -214.748,3648 bis 214.748,3647 zu speichern (zum Speichern eines Werts sind 4 Byte erforderlich). Wenn eine größere Anzahl von Dezimalstellen erforderlich ist, verwenden Sie stattdessen den Dezimaldatentyp.


1

Manchmal müssen Sie auf weniger als einen Cent gehen, und es gibt internationale Währungen, die sehr große Dämonisierungen verwenden. Beispielsweise können Sie Ihren Kunden 0,088 Cent pro Transaktion in Rechnung stellen. In meiner Oracle-Datenbank sind die Spalten als NUMBER (20,4) definiert.


1

Wenn Sie in der Datenbank arithmetische Operationen ausführen (Abrechnungsraten multiplizieren usw.), möchten Sie wahrscheinlich viel mehr Präzision, als die Leute hier vorschlagen, aus den gleichen Gründen, die Sie niemals hätten Sie möchten weniger als einen Gleitkommawert mit doppelter Genauigkeit im Anwendungscode verwenden.


Das habe ich mir gedacht, aber in Bezug auf die Wechselkurse (dh die Umrechnung von Zimbawe-Dollar in USD). Ich werde einige Experimente mit den von mir verwendeten Datenbanken (psql, sqlite) durchführen, um zu sehen, wie sie mit Rundungen auf sehr kleinen Dezimalstellen umgehen.
Ivan

Haben Floats auch keine Genauigkeitsprobleme in einigen DBMS / Sprachen?
Ivan

2
Floats haben Genauigkeitsprobleme in ALLEN Sprachen.
Marcus Downing

Die heutzutage häufigste Empfehlung ist die Verwendung willkürlicher Präzision (denken BigDecimal), aber lange Zeit war es doppelte Präzision (denken doublestatt float). Willkürliche Präzision hat in einigen Fällen auch erhebliche Leistungseinbußen. Testen ist definitiv der richtige Ansatz.
Hank Gay

0

Wenn Sie IBM Informix Dynamic Server verwenden, haben Sie einen MONEY-Typ, der eine geringfügige Variante des DECIMAL- oder NUMERIC-Typs darstellt. Es ist immer ein Festkommatyp (während DECIMAL ein Gleitkommatyp sein kann). Sie können eine Skala von 1 bis 32 und eine Genauigkeit von 0 bis 32 angeben (standardmäßig eine Skala von 16 und eine Genauigkeit von 2). Je nachdem, was Sie speichern müssen, können Sie DECIMAL (16,2) verwenden - immer noch groß genug, um das US-Bundesdefizit auf den nächsten Cent genau zu halten - oder Sie können einen kleineren Bereich oder mehr Dezimalstellen verwenden.


0

Ich würde denken, dass Ihre oder die Anforderungen Ihres Kunden zu einem großen Teil bestimmen sollten, welche Präzision und Skalierung verwendet werden soll. Für die E-Commerce-Website, an der ich gerade arbeite und die sich nur mit Geld in GBP befasst, musste ich sie auf Dezimal (6, 2) halten.


0

Eine späte Antwort hier, aber ich habe verwendet

DECIMAL(13,2)

was ich richtig denke, sollte bis zu 99.999.999.999,99 erlauben.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.