Ich kann viele Fragen zu Bibliotheken finden, mit denen Beträge in einer bestimmten Währung dargestellt werden können. Und über die uralte Frage, warum Sie keine Währung als Gleitkommazahl nach IEEE 754 speichern sollten. Aber ich kann anscheinend nichts mehr finden. Sicher gibt es viel mehr zu wissen über die Verwendung von Währungen in der realen Welt. Ich interessiere mich besonders für das, was Sie wissen müssen, um es im physischen Gebrauch darzustellen (z. B. mit dem Dollar haben Sie nie eine Genauigkeit von weniger als 0,01 US-Dollar, was die Darstellung als ganze Zahl von Cent ermöglicht).
Es ist jedoch schwierig, Annahmen darüber zu treffen, wie vielseitig Ihr Programm ist, wenn Sie nur die populären westlichen Währungen kennen (wie Dollar, Euro und Pfund). Was ist sonst relevantes Wissen in einer rein programmatischen Perspektive? Ich mache mir keine Sorgen um das Thema Bekehrung.
Was müssen wir insbesondere wissen, um Werte in einer bestimmten Währung speichern und ausdrucken zu können?
Antworten:
Ja wirklich?
Sie können Zoll auch in IEEE 754- Gleitkommazahlen speichern . Sie speichern genau so, wie Sie es erwarten würden.
Sie können beliebig viel Geld in IEEE 754- Gleitkommazahlen speichern, die Sie mithilfe der Häkchen speichern können, die ein Lineal in Bruchteile eines Zolls teilen.
Warum? Denn wenn Sie IEEE 754 verwenden , speichern Sie es so.
Die Sache mit Zoll ist, dass sie in zwei Hälften geteilt sind. Das Wichtigste an den meisten Währungen ist, dass sie in Zehntel unterteilt sind (einige Arten sind es nicht, aber lassen Sie uns konzentriert bleiben).
Dieser Unterschied wäre nicht allzu verwirrend, außer dass für die meisten Programmiersprachen die Ein- und Ausgabe von IEEE 754 erfolgt Gleitkommazahlen in Dezimalzahlen ausgedrückt wird! Das ist sehr seltsam, weil sie nicht in Dezimalzahlen gespeichert sind.
Aus diesem Grund kann man nie sehen, wie die Bits seltsame Dinge tun, wenn man den Computer zum Speichern auffordert
0.1
. Sie sehen die Verrücktheit nur, wenn Sie dagegen rechnen und es hat seltsame Fehler.Aus Josh Blochs effektivem Java :
Produziert
0.6100000000000001
Was am meisten darüber aussagt, ist nicht das
1
Sitzen dort rechts. Es sind die seltsamen Zahlen, die verwendet werden mussten, um es zu bekommen. Anstatt das beliebteste Beispiel zu verwenden,0.1
müssen wir ein Beispiel verwenden, das das Problem zeigt und die Rundung vermeidet, die es verbergen würde.Warum funktioniert das zum Beispiel?
Produziert
-0.01
Weil wir Glück hatten.
Ich hasse Probleme, die schwer zu diagnostizieren sind, weil ich manchmal "Glück" habe.
IEEE 754 kann 0.1 einfach nicht genau speichern. Aber wenn Sie ihn auffordern, 0.1 zu speichern und dann zum Drucken aufzufordern, wird 0.1 angezeigt und Sie werden denken, dass alles in Ordnung ist. Es ist nicht in Ordnung, aber das kann man nicht sehen, weil es rundet, um wieder auf 0.1 zu kommen.
Manche Leute verwechseln andere, indem sie diese Diskrepanzen als Rundungsfehler bezeichnen. Nein, das sind keine Rundungsfehler. Das Runden macht, was es soll, und verwandelt, was keine Dezimalstelle ist, in eine Dezimalstelle, damit es auf dem Bildschirm gedruckt werden kann.
Dadurch wird jedoch die Diskrepanz zwischen der Anzeige und der Speicherung der Nummer ausgeblendet. Der Fehler ist bei der Rundung nicht aufgetreten. Es geschah, als Sie beschlossen, eine Zahl in ein System einzufügen, das sie nicht genau speichern kann, und davon ausgegangen sind, dass sie genau dann gespeichert wird, wenn dies nicht der Fall ist.
Niemand erwartet, dass π genau in einem Taschenrechner gespeichert wird, und es gelingt ihm, einwandfrei damit zu arbeiten. Das Problem besteht also nicht einmal in der Präzision. Es geht um die erwartete Präzision. Computer zeigen ein Zehntel so an
0.1
wie unsere Taschenrechner, daher erwarten wir, dass sie ein Zehntel so perfekt speichern wie unsere Taschenrechner. Sie tun es nicht. Was überrascht, da Computer teurer sind.Lassen Sie mich Ihnen die Nichtübereinstimmung zeigen:
Beachten Sie, dass 1/2 und 0,5 perfekt ausgerichtet sind. Aber 0.1 passt einfach nicht zusammen. Sicher können Sie näher kommen, wenn Sie durch 2 teilen, aber Sie werden es nie genau treffen. Und wir brauchen jedes Mal mehr Bits, wenn wir durch 2 dividieren. Für die Darstellung von 0,1 bei jedem System, das durch 2 dividiert, ist eine unendliche Anzahl von Bits erforderlich. Meine Festplatte ist einfach nicht so groß.
Also 754 IEEE stoppt versuchen , wenn es von Bits abläuft. Das ist schön, denn ich brauche Platz auf meiner Festplatte für ... Familienfotos. Nicht wirklich. Familienfotos. : P
Wie auch immer, was Sie eingeben und was Sie sehen, sind die Dezimalstellen (rechts), aber was Sie speichern, sind die Bikimalstellen (links). Manchmal sind die vollkommen gleich. Manchmal sind sie nicht. Manchmal SIEHT es so aus, als wären sie gleich, wenn sie es einfach nicht sind. Das ist die Rundung.
Wenn Sie mit meinem dezimalbasierten Geld umgehen, verwenden Sie bitte keine Floats oder Doubles.
Wenn Sie sicher sind, dass Dinge wie zehntel Pennies nicht betroffen sind, dann lagern Sie nur Pennies. Wenn Sie dies nicht tun, ermitteln Sie die kleinste Einheit dieser Währung und verwenden Sie diese. Wenn Sie nicht können, verwenden Sie so etwas wie BigDecimal .
Mein Vermögen wird wahrscheinlich immer in eine 64-Bit-Ganzzahl passen, aber Dinge wie BigInteger funktionieren gut für Projekte, die größer sind. Sie sind nur langsamer als einheimische Typen.
Herauszufinden, wie man es aufbewahrt, ist nur das halbe Problem. Denken Sie daran, dass Sie es auch anzeigen können müssen. Ein gutes Design wird diese beiden Dinge trennen. Das eigentliche Problem bei der Verwendung von Schwimmern ist, dass diese beiden Dinge miteinander verfilzt sind.
quelle
"Bibliotheken" sind nicht erforderlich, es sei denn, die Standardbibliothek Ihrer Sprache enthält bestimmte Datentypen, wie ich noch erläutern werde.
Ganz einfach, Sie brauchen eine Festkommadezimalzahl , keine Gleitkommadezimalzahl . Beispielsweise könnte die BigDecimal- Klasse von Java zum Speichern eines Währungsbetrags verwendet werden. Andere moderne Sprachen verfügen über ähnliche integrierte Typen, einschließlich C # und Python . Die Implementierungen variieren, sie speichern jedoch normalerweise eine Zahl als Ganzzahl mit der Dezimalstelle als separatem Datenelement. Dies ergibt eine genaue Genauigkeit, selbst wenn eine Arithmetik ausgeführt wird, die ungerade Reste (z. B. 0,0000001) mit IEEE-Gleitkommazahlen ergeben würde.
Es gibt einige wichtige Punkte.
Verwenden Sie einen tatsächlichen Dezimaltyp anstelle eines Gleitkommas.
Verstehen Sie, dass ein Währungsbetrag aus zwei Komponenten besteht: einem Wert (5,63) und einem Währungscode oder -typ (USD, CAD, GBP, EUR usw.). Manchmal können Sie den Währungscode ignorieren, manchmal ist es wichtig. Was ist, wenn Sie an einem Finanz- oder Einzelhandels- / E-Commerce-System arbeiten, das mehrere Währungen zulässt? Was passiert, wenn Sie versuchen, einem Kunden Geld in CAD abzunehmen, dieser aber mit MXN bezahlen möchte? Sie benötigen einen "Geld" -Typ mit einem Währungscode und einem Währungsbetrag, um diese Werte mischen zu können (auch Wechselkurs, aber ich möchte mit einer Tangente nicht zu weit kommen). Gleichzeitig muss sich meine persönliche Finanzsoftware darüber keine Gedanken machen, da alles in USD lautet (es kann Währungen mischen, aber ich muss es nie).
Während eine Währung in der Praxis möglicherweise die kleinste physische Einheit hat (CAD und USD haben Cent, JPY ist nur ... Yen), ist es möglich, kleiner zu werden. In der Antwort von CandiedOrange werden die Kraftstoffpreise in Zehntel Cent angegeben. Meine Grundsteuern werden als Mühlen pro Dollar oder Zehntel Cent (1/1000 eines USD-Dollars) veranschlagt. Beschränken Sie sich nicht auf 0,01 USD. Während Sie diese Werte möglicherweise die meiste Zeit anzeigen , sollten Ihre Typen kleinere zulassen (die oben genannten Dezimaltypen).
Zwischenberechnungen müssen mit Sicherheit präziser sein als ein Cent. Ich habe an Einzelhandels- / E-Commerce-Systemen gearbeitet, bei denen interne Werte intern auf 0,00000001 US-Dollar gerundet wurden. Unendliche Genauigkeit wird normalerweise nicht von Dezimaltypen (oder SQL) unterstützt, daher muss es eine gewisse Beschränkung geben. Wenn Sie beispielsweise 1/3 mit Javas BigDecimal teilen, wird eine Ausnahme ohne RoundingMode oder MathContext ausgelöst, da der Wert nicht genau dargestellt werden kann.
Dies ist jedoch in bestimmten Fällen kritisch. Nehmen wir an, Sie haben einen Benutzer mit sechs Artikeln im Warenkorb und er geht zur Kasse. Ihr System muss Steuern berechnen, und zwar pro Artikel, da Artikel möglicherweise anders besteuert werden. Wenn Sie die Steuern für jeden Artikel aufrunden, kann es zu Rundungsfehlern auf Transaktions- / Wagenebene kommen. Ein Ansatz, um dies zu beheben, könnte darin bestehen, Steuern auf mehr Dezimalstellen pro Artikel zu speichern, die Gesamtsumme für die gesamte Transaktion abzurufen und jeden Artikel zu runden, damit die Gesamtsteuer korrekt ist (möglicherweise rundet ein Artikel auf, ein anderer ab).
Das Wichtigste dabei ist, dass für die richtigen Leute etwas so Wichtiges wie das Runden von Pfennigen sehr wichtig sein kann (z. B. einige meiner früheren Kunden, die die staatlichen Umsatzsteuern im Namen ihrer Kunden zahlen mussten). Dies sind jedoch alles gelöste Probleme. Behalten Sie die obigen Punkte im Hinterkopf und experimentieren Sie selbst, und Sie werden lernen, indem Sie dies tun.
quelle
Ein Ort, an dem viele Entwickler mit einer einzigen Datendarstellung für jede Währung konfrontiert sind, sind In-App-Käufe für iOS-Anwendungen. Ihr Kunde kann mit einem Geschäft in nahezu jedem Land der Welt verbunden sein. In diesem Fall erhalten Sie einen Kaufpreis, der aus einer doppelten Genauigkeitszahl und einem Währungscode besteht.
Sie müssen sich bewusst sein, dass die Zahlen groß sein können. Es gibt Währungen, in denen der Gegenwert von beispielsweise zehn Dollar mehr als 100.000 Einheiten beträgt. Und wir sind froh, dass es derzeit keine Währungen wie den simbabwischen Dollar gibt, in denen Sie hundert Billionen Banknoten haben könnten!
Für die Anzeige von Währungen benötigen Sie eine Bibliothek - Sie haben keine Chance, alles selbst in Ordnung zu bringen. Die Anzeige hängt von zwei Faktoren ab: dem Währungscode und dem Gebietsschema des Benutzers. Stellen Sie sich vor, wie US-Dollar und kanadische Dollar mit einem US-Gebietsschema und einem kanadischen Gebietsschema angezeigt werden: In den USA haben Sie $ vs CAN $ und in Kanada haben Sie US $ vs $. Ich hoffe, das ist in das Betriebssystem integriert, oder Sie haben eine gute Bibliothek.
Bei Berechnungen endet jede Berechnung mit einem Rundungsschritt. Sie müssen herausfinden, wie Sie diese Rundung legal durchführen müssen . Das ist kein Programmierproblem, sondern ein rechtliches Problem. Wenn Sie beispielsweise die Mehrwertsteuer in Großbritannien berechnen, müssen Sie die Steuer pro Artikel oder pro Artikelzeile berechnen und auf Cent abrunden. Worauf Sie runden, hängt von der Währung ab. Aber die Regeln hängen natürlich vom Land ab. Sie können nicht erwarten, dass eine in Großbritannien rechtlich korrekte Berechnung in Japan rechtlich korrekt ist und umgekehrt.
quelle
Einige Beispielprobleme im Zusammenhang mit dem Gebietsschema:
Ein weiteres mögliches Problem ist, dass die Währung möglicherweise in Gleitkommazahlen konvertiert werden muss, auch wenn Sie sie korrekt in Festkommazahlen speichern, da die zum Drucken verwendete Bibliothek nur Gleitkommazahlen unterstützt.
quelle