it-swarm.com.de

Die beste Möglichkeit, Währungswerte in C++ zu speichern

Ich weiß, dass ein Float aufgrund von Rundungsfehlern nicht zum Speichern von Währungswerten geeignet ist. Gibt es eine Standardmethode, um Geld in C++ zu repräsentieren? 

Ich habe in der Boost-Bibliothek nachgesehen und nichts darüber gefunden. In Java scheint BigInteger der Weg zu sein, aber ich konnte in C++ keine Entsprechung finden. Ich könnte meine eigene Geldklasse schreiben, aber ich würde es lieber nicht tun, wenn etwas getestet wird.

53
Javier Ramos

Speichern Sie es nicht als Cent, da Sie Fehler sammeln, wenn Sie Steuern und Zinsen relativ schnell multiplizieren. Behalten Sie mindestens zwei weitere signifikante Stellen bei: 12,45 USD würden als 124.500 gespeichert. Wenn Sie es in einer 32-Bit-Ganzzahl mit Vorzeichen speichern, haben Sie 200.000 US-Dollar (positiv oder negativ), mit denen Sie arbeiten können. Wenn Sie größere Zahlen oder mehr Genauigkeit benötigen, bietet Ihnen eine 64-Bit-Ganzzahl mit Vorzeichen wahrscheinlich den gesamten Platz, den Sie für lange Zeit benötigen.

Es kann hilfreich sein, diesen Wert in eine Klasse zu packen, um Ihnen einen Platz zu geben, in dem Sie diese Werte erstellen, mit ihnen rechnen und für die Anzeige formatieren können. Damit haben Sie auch einen zentralen Platz, um die Währung, in der es gelagert wird, zu transportieren (USD, CAD, EURO usw.).

30
Eclipse

Nachdem ich dies in tatsächlichen Finanzsystemen behandelt habe, kann ich Ihnen sagen, dass Sie wahrscheinlich eine Zahl mit einer Genauigkeit von mindestens 6 Dezimalstellen verwenden möchten (vorausgesetzt, USD). Hoffentlich gehen Sie hier nicht aus dem Gleichgewicht, da Sie über Währungswerte sprechen. Es gibt Vorschläge für das Hinzufügen von Dezimaltypen zu C++, aber ich kenne noch keine, die tatsächlich verfügbar sind.

Der beste native C++ - Typ, der hier verwendet werden soll, wäre long double.

Das Problem bei anderen Ansätzen, die einfach ein int verwenden, besteht darin, dass Sie mehr als nur Ihre Cents speichern müssen. Oft werden Finanztransaktionen mit nicht ganzzahligen Werten multipliziert, was zu Problemen führen wird, da 100,25 $ in 10025 * 0,000123523 (z. B. APR) zu Problemen führen. Sie landen irgendwann im Floating-Point-Land und die Konvertierungen werden Sie viel kosten.

Nun tritt das Problem in den meisten einfachen Situationen nicht auf. Ich gebe dir ein genaues Beispiel:

Wenn Sie mehrere Tausend Währungswerte angeben, multiplizieren Sie jeden mit einem Prozentsatz und addieren Sie diese. Dann erhalten Sie eine andere Zahl, als wenn Sie die Summe mit diesem Prozentsatz multipliziert hätten, wenn Sie nicht genügend Dezimalstellen vorhalten. In manchen Situationen kann dies funktionieren, aber Sie werden oft ziemlich schnell ein paar Pfennig abnehmen. Nach meiner allgemeinen Erfahrung muss sichergestellt werden, dass Sie eine Genauigkeit von bis zu 6 Dezimalstellen einhalten (sicherstellen, dass die verbleibende Genauigkeit für den gesamten Zahlenteil verfügbar ist). 

Verstehen Sie auch, dass es egal ist, mit welchem ​​Typ Sie es speichern, wenn Sie weniger genau rechnen. Wenn Ihre Berechnungen in einem Land mit einfacher Genauigkeit ausgeführt werden, ist es nicht wichtig, wenn Sie sie mit doppelter Genauigkeit speichern. Ihre Genauigkeit stimmt mit der am wenigsten genauen Berechnung überein.


Nun, das heißt, wenn Sie keine anderen Berechnungen durchführen als einfache Additionen oder Subtraktionen und dann die Zahl speichern, dann werden Sie in Ordnung sein, aber sobald etwas komplexeres als das auftaucht, werden Sie in Schwierigkeiten geraten.

21
Orion Adrian

Schauen Sie sich die relativ neue Intelr Decimal Floating-Point Math Library an. Es ist speziell für Finanzanwendungen gedacht und implementiert einige der neuen Standards für die binäre Gleitpunktarithmetik (IEEE 754r) .

20
jblocksom

Das größte Problem ist die Rundung!

19% von 42,50 € = 8.075 €. Aufgrund der deutschen Rundungsregeln sind dies 8,08 €. Das Problem ist, dass 8.075 (zumindest auf meiner Maschine) nicht als doppelt dargestellt werden können. Selbst wenn ich die Variable im Debugger auf diesen Wert ändere, lande ich bei 8.0749999 ...

Und hier versagt meine Rundungsfunktion (und jede andere auf Fließkomma-Logik, die mir einfällt), da sie 8,07 € ergibt. Die signifikante Ziffer ist 4 und daher wird der Wert abgerundet. Das ist eindeutig falsch und Sie können nichts dagegen tun, es sei denn, Sie vermeiden die Verwendung von Gleitkommawerten, wo immer dies möglich ist.

Es funktioniert gut, wenn Sie 42,50 € als Integer 42500000 darstellen.

42500000 * 19/100 = 8075000. Jetzt können Sie die Rundungsregel über 8080000 anwenden. Diese kann aus Darstellungsgründen leicht in einen Währungswert umgewandelt werden. 8,08 €.

Aber ich würde das immer in einer Klasse zusammenfassen.

10
user331471

Ich würde vorschlagen, dass Sie eine Variable für die Anzahl der Cents anstelle von Dollar beibehalten. Das sollte die Rundungsfehler beseitigen. Die Anzeige im Format Dollar/Cent sollte ein Anliegen sein.

8
Rontologist

Für welchen Typ Sie sich auch entscheiden, ich würde empfehlen, ihn in einem "Typedef" einzupacken, damit Sie ihn zu einem anderen Zeitpunkt ändern können.

5
Jordan Parmer

Sie können den Dezimaldatentyp ausprobieren:

https://github.com/vpiotr/decimal_for_cpp

Zur Speicherung von geldorientierten Werten (Geldwert, Wechselkurs, Zinssatz) und benutzerdefinierter Genauigkeit. Bis zu 19 Ziffern.

Es ist eine reine Header-Lösung für C++.

5
Piotr

Kennen Sie Ihren Datenbereich.

Ein Float ist nur für eine Genauigkeit von 6 bis 7 Stellen geeignet, dh maximal +9999,99 ohne Rundung. Für die meisten Finanzanwendungen ist es nutzlos. 

Ein Doppel ist gut für 13 Ziffern, also: + -99,999,999,999,99, Seien Sie vorsichtig, wenn Sie große Zahlen verwenden. Erkennen Sie, wie zwei ähnliche Ergebnisse subtrahiert werden, um einen Großteil der Genauigkeit (siehe ein Buch zur Numerischen Analyse für mögliche Probleme). 

32-Bit-Ganzzahl ist gut bis + -2Milliarden (bei der Skalierung auf Pennys fallen 2 Dezimalstellen an)

64-Bit-Integer verarbeitet jedes Geld. Seien Sie jedoch vorsichtig beim Konvertieren und multiplizieren Sie diese mit verschiedenen Raten in Ihrer App, die möglicherweise Floats/Doubles sind.

Der Schlüssel ist, Ihre Problemdomäne zu verstehen. Welche gesetzlichen Anforderungen haben Sie an die Richtigkeit? Wie werden die Werte angezeigt? Wie oft findet eine Konvertierung statt? Benötigen Sie Internationalisierung? Stellen Sie sicher, dass Sie diese Fragen beantworten können, bevor Sie Ihre Entscheidung treffen.

5
Dan Hewett

Dies hängt von Ihren geschäftlichen Anforderungen in Bezug auf die Rundung ab. Am sichersten ist es, eine ganze Zahl mit der erforderlichen Genauigkeit zu speichern und zu wissen, wann und wie Rundungen angewendet werden.

4
Douglas Mayle

Sie sagen, Sie haben in der Boost-Bibliothek nachgeschlagen und dort nichts gefunden ... Aber dort haben Sie multiprecision/cpp_dec_float , das sagt:

Der Radix dieses Typs ist 10. Daher kann er sich geringfügig von den Basis-2-Typen unterscheiden.

Wenn Sie also bereits Boost verwenden, sollte dies gut für die Währungswerte und -vorgänge sein, da die Basisnummer 10 und die Genauigkeit von 50 oder 100 Ziffern (viel) gilt.

Sehen:

#include <iostream>
#include <iomanip>
#include <boost/multiprecision/cpp_dec_float.hpp>

int main()
{
    float bogus = 1.0 / 3.0;
    boost::multiprecision::cpp_dec_float_50 correct = 1.0 / 3.0;

    std::cout << std::setprecision(16) << std::fixed 
              << "float: " << bogus << std::endl
              << "cpp_dec_float: " << correct << std::endl;

    return 0;
}

Ausgabe:

schwimmer: 0,3333333432674408

cpp_dec_float: 0,3333333333333333

* Ich sage nicht, dass Float (Basis 2) schlecht ist und Dezimalzahl (Basis 10) gut ist. Sie benehmen sich einfach anders ...

** Ich weiß, dass dies ein alter Beitrag ist und der :: boost :: multiprecision wurde 2013 eingeführt. Ich wollte es hier anmerken.

3
Fernando

Ganze Zahlen, immer - speichern Sie es als Cent (oder was auch immer Ihre niedrigste Währung ist, für die Sie programmieren.) Das Problem ist, dass, egal was Sie mit Floating Point tun, Sie irgendwann eine Situation finden werden, in der sich die Berechnung unterscheidet es im Fließkomma. Eine Rundung in letzter Minute ist nicht die Antwort, da reale Währungsberechnungen fortlaufend gerundet werden.

Sie können das Problem auch nicht vermeiden, indem Sie die Reihenfolge der Vorgänge ändern. Dies schlägt fehl, wenn Sie einen Prozentsatz haben, bei dem Sie keine korrekte binäre Darstellung haben. Buchhalter werden ausflippen, wenn Sie um einen einzigen Cent aus sind.

2
Loren Pechtel

Ich würde empfehlen, ein Long Int zu verwenden, um die Währung in der kleinsten Währung zu speichern (z. B. wäre amerikanisches Geld Cents), wenn eine Dezimalwährung verwendet wird.

Sehr wichtig: Achten Sie darauf, alle Ihre Währungswerte entsprechend ihrer tatsächlichen Bezeichnung zu benennen. (Beispiel: account_balance_cents) Dadurch werden viele Probleme auf der ganzen Linie vermieden.

(Ein anderes Beispiel, in dem dies auftritt, sind Prozentsätze. Nennen Sie niemals einen Wert "XXX_percent", wenn er tatsächlich ein Verhältnis enthält, das nicht mit 100 multipliziert wird.)

1

Die Lösung ist einfach und kann mit beliebiger Genauigkeit als verschobene Ganzzahl gespeichert werden. Beim Einlesen in einen Double-Float-Vorgang werden jedoch weniger Rundungsfehler berechnet. Wenn Sie dann in der Datenbank speichern, multiplizieren Sie diese mit der gewünschten Integer-Genauigkeit. Fügen Sie jedoch +/- 1/10 hinzu, um Abschneidefehler zu kompensieren, oder um +/- 51/100 zu runden.

1
Laurie

schreiben Sie Ihr eigenes Geld ( http://junit.sourceforge.net/doc/testinfected/testing.htm ) oder die Currency () - Klasse (je nachdem, was Sie brauchen). und testen Sie es.

0
Ray Tayek

Unser Finanzinstitut verwendet "doppelt". Da wir ein "Fixed Income" -Shop sind, haben wir viele unangenehme komplizierte Algorithmen, die sowieso Double verwenden. Der Trick besteht darin, sicherzustellen, dass Ihre Endbenutzer-Präsentation die Genauigkeit von double nicht überschreitet. Wenn wir beispielsweise eine Liste von Trades mit einem Gesamtbetrag von mehreren Billionen Dollar haben, müssen wir sicher sein, dass wir aufgrund von Rundungsproblemen keinen Müll drucken.

0
Arkadiy

Ich würde für 32-Bit mit Vorzeichen versehen und für 64-Bit mit Vorzeichen versehen. Dadurch erhalten Sie maximale Speicherkapazität für die zugrunde liegende Menge. Ich würde dann zwei benutzerdefinierte Manipulatoren entwickeln. Einer, der diese Menge auf der Grundlage von Wechselkursen umrechnet, und einer, der diese Menge in der Währung Ihrer Wahl formatiert. Sie können mehr Manipulatoren für verschiedene Finanzvorgänge/-regeln entwickeln.

0
user2074102

Die Bibliothek " GMP " verfügt über "bignum" -Implementierungen, die Sie für Berechnungen mit ganzzahligen Ganzzahlen verwenden können, die für den Umgang mit Geld erforderlich sind. Siehe auch die Dokumentation zu mpz_class(Achtung: Dies ist furchtbar unvollständig, jedoch wird das gesamte Spektrum der Rechenoperatoren bereitgestellt).

0
Greg Rogers

Eine Option besteht darin, $ 10,01 als 1001 zu speichern und alle Berechnungen in Pennies durchzuführen, wobei die Werte durch 100D geteilt werden.

Oder verwenden Sie Schwimmer und runden Sie nur im letztmöglichen Moment.

Oft können die Probleme durch Änderung der Reihenfolge der Vorgänge gemindert werden.

Verwenden Sie anstelle des Werts * .10 für einen Rabatt von 10% (Wert * 10)/100, was erheblich helfen wird. (Denken Sie daran .1 ist eine sich wiederholende Binärdatei)

0
Chris Cudmore

Speichern Sie den Dollar- und Cent-Betrag als zwei separate Ganzzahlen.

0
kmiklas