Ist ein Doppel für Geld wirklich ungeeignet?

70

Ich sage immer in c #, dass eine Variable vom Typ double nicht für Geld geeignet ist. Alle seltsamen Dinge könnten passieren. Aber ich kann anscheinend kein Beispiel erstellen, um einige dieser Probleme zu demonstrieren. Kann jemand ein solches Beispiel geben?

(Bearbeiten; dieser Beitrag wurde ursprünglich mit C # markiert; einige Antworten beziehen sich auf bestimmte Details von decimal, was daher bedeutet System.Decimal).

(Bearbeiten 2: Ich habe speziell nach C # -Code gefragt, daher denke ich nicht, dass dies nur sprachunabhängig ist.)

Doekman
quelle

Antworten:

115

Sehr, sehr ungeeignet. Verwenden Sie eine Dezimalstelle.

double x = 3.65, y = 0.05, z = 3.7;
Console.WriteLine((x + y) == z); // false

(Beispiel von Jons Seite hier - empfohlene Lektüre ;-p)

Marc Gravell
quelle
48
Verdammt, wenn ich gewusst hätte, dass ich ein Beispiel auf meiner eigenen Seite habe, hätte ich mir kein anderes ausgedacht;)
Jon Skeet
34

Sie erhalten ungerade Fehler, die effektiv durch Rundungen verursacht werden. Darüber hinaus sind Vergleiche mit exakten Werten äußerst schwierig - normalerweise müssen Sie eine Art Epsilon anwenden, um zu überprüfen, ob der tatsächliche Wert "nahe" an einem bestimmten Wert liegt.

Hier ist ein konkretes Beispiel:

using System;

class Test
{
    static void Main()
    {
        double x = 0.1;
        double y = x + x + x;
        Console.WriteLine(y == 0.3); // Prints False
    }
}
Jon Skeet
quelle
Wenn Sie einen Service nutzen, der doppelte Währungswerte zurückgibt, die Sie nicht kontrollieren können, müssen Sie bei der Konvertierung in Dezimalwerte darüber nachdenken? Präzisionsverlust etc ...
vikingben
1
@vikingben: Absolut - im Grunde ist das eine kaputte Art, Dinge zu tun, und Sie müssen herausfinden, wie Sie die Daten am besten interpretieren können.
Jon Skeet
7

Ja, es ist ungeeignet.

Wenn ich mich richtig erinnere, hat double ungefähr 17 signifikante Zahlen, so dass Rundungsfehler normalerweise weit hinter dem Dezimalpunkt auftreten. Die meisten Finanzsoftware verwendet 4 Dezimalstellen hinter dem Dezimalpunkt, sodass 13 Dezimalstellen zur Verfügung stehen, sodass die maximale Anzahl, mit der Sie für einzelne Operationen arbeiten können, immer noch sehr viel höher ist als die Staatsverschuldung der USA. Rundungsfehler summieren sich jedoch mit der Zeit. Wenn Ihre Software längere Zeit läuft, verlieren Sie möglicherweise Cent. Bestimmte Operationen werden dies noch schlimmer machen. Zum Beispiel führt das Hinzufügen großer Mengen zu kleinen Mengen zu einem erheblichen Genauigkeitsverlust.

Sie benötigen Festkomma-Datentypen für Geldoperationen. Den meisten Menschen macht es nichts aus, wenn Sie hier und da einen Cent verlieren, aber Buchhalter sind nicht wie die meisten Menschen.

Bearbeiten
Laut dieser Website http://msdn.microsoft.com/en-us/library/678hzkk9.aspx Doppel haben tatsächlich 15 bis 16 signifikante Ziffern anstelle von 17.

@ Jon Skeet Dezimalzahl ist aufgrund ihrer höheren Genauigkeit, 28 oder 29 signifikanten Dezimalstellen, besser geeignet als doppelt. Dies bedeutet eine geringere Wahrscheinlichkeit, dass akkumulierte Rundungsfehler signifikant werden. Festkomma-Datentypen (dh Ganzzahlen, die Cent oder 100stel Cent darstellen, wie ich sie verwendet habe) wie Boojum-Erwähnungen sind tatsächlich besser geeignet.

Mendelt
quelle
Beachten Sie, dass System.Decimal, der in .NET vorgeschlagene Typ, immer noch ein Gleitkommatyp ist - es ist jedoch eher ein Gleitkomma-Dezimalpunkt als ein Gleitkomma-Binärpunkt. Das ist in den meisten Fällen wichtiger als eine feste Präzision, vermute ich.
Jon Skeet
1
Genau das ist das Problem. Die Währung ist heutzutage normalerweise dezimal. Vor der Dezimalisierung der US-Aktienmärkte wurden jedoch binäre Brüche verwendet (ich sah an einem Punkt 256stel und sogar 1024stel), und daher wären Doppelte für Aktienkurse angemessener gewesen als Dezimalstellen! Pfund Sterling vor der Dezimalisierung wäre allerdings bei 960 Kilometern ein echtes Problem gewesen; Das ist weder dezimal noch binär, aber es bietet sicherlich eine großzügige Vielfalt an Primfaktoren für einfache Brüche.
Jeffrey Hantin
1
Noch wichtiger als nur ein Dezimal-Gleitkomma, decimalder Ausdruck x + 1 != xist immer wahr. Außerdem bleibt die Präzision erhalten, sodass Sie den Unterschied zwischen 1und erkennen können 1.0.
Gabe
@Gabe: Diese Eigenschaften sind nur dann sinnvoll, wenn man seine Werte so skaliert, dass ein Wert von 1 die kleinste Währungseinheit darstellt. Ein DecimalWert kann rechts vom Dezimalpunkt an Genauigkeit verlieren, ohne dass ein Problem angezeigt wird.
Supercat
doublehat 15,9 signifikante Dezimalstellen, die nur ganzzahlige Werte berücksichtigen. Die Situation nach dem Dezimalpunkt ist wertabhängig.
user207421
5

Da decimalein Skalierungsfaktor von Vielfachen von 10 verwendet wird, können Zahlen wie 0,1 genau dargestellt werden. Im Wesentlichen repräsentiert der Dezimaltyp doubledies als 1/2 ^ 1, während a dies als 104857/2 ^ 20 darstellen würde (in Wirklichkeit wäre es eher eine wirklich große Zahl / 2 ^ 1023).

A decimalkann jeden beliebigen Basis-10-Wert mit bis zu 28/29 signifikanten Stellen (wie 0,1) genau darstellen. A doublekann nicht.

Richard Poole
quelle
3
Dezimal hat keine 96 signifikanten Stellen. Es hat 96 signifikante Bits . Dezimal hat ungefähr 28 signifikante Stellen.
Jon Skeet
In welcher Sprache sprechen Sie vom Dezimaltyp? Oder unterstützen alle Sprachen, die diesen Typ unterstützen, ihn genauso? Vielleicht möchten Sie angeben.
Adam Davis
@Adam - Dieser Beitrag hatte ursprünglich das C # -Tag, daher sprechen wir speziell über System.Decimal.
Marc Gravell
Ups, gut gesehener Jon! Korrigiert. Adam, ich spreche von C # gemäß der Frage. Haben andere Sprachen einen Typ namens Dezimal?
Richard Poole
@Richard: Nun, alle Sprachen, die auf .NET basieren, tun dies, da System.Decimal kein eindeutiger C # -Typ ist, sondern ein .NET-Typ.
Ehrfurcht
4

Mein Verständnis ist, dass die meisten Finanzsysteme Währung mit ganzen Zahlen ausdrücken - dh alles in Cent zählen.

Die doppelte IEEE-Genauigkeit kann tatsächlich alle ganzen Zahlen genau im Bereich von -2 ^ 53 bis + 2 ^ 53 darstellen. (Hacker's Delight, S. 262) Wenn Sie nur Addition, Subtraktion und Multiplikation verwenden und alles auf ganze Zahlen innerhalb dieses Bereichs beschränken, sollten Sie keinen Genauigkeitsverlust feststellen. Ich wäre jedoch sehr vorsichtig bei Teilungen oder komplexeren Operationen.

Boojum
quelle
Wenn Sie jedoch nur Ganzzahlen verwenden möchten, verwenden Sie zunächst einen Ganzzahltyp.
Jon Skeet
2
Heh - int64_t kann alle ganzen Zahlen genau im Bereich von -2 ^ 63 bis + 2 ^ 63-1 darstellen. Wenn Sie nur Addition, Subtraktion und Multiplikation verwenden und alles auf ganze Zahlen innerhalb dieses Bereichs beschränken, sollten Sie keinen Genauigkeitsverlust feststellen. Ich wäre jedoch sehr vorsichtig bei der Teilung.
Steve Jessop
Einige veraltete Systeme, die (leider?) Noch verwendet werden, unterstützen doublejedoch keinen 64-Bit-Integer-Typ. Ich würde vorschlagen, dass die Durchführung von Berechnungen als double, skaliert, dass jede semantisch erforderliche Rundung immer auf ganze Einheiten erfolgt, der effizienteste Ansatz ist.
Supercat
3

Die Verwendung von double ist ungeeignet, wenn Sie nicht wissen, was Sie tun.

"doppelt" kann einen Betrag von einer Billion Dollar mit einem Fehler von 1/90 Cent darstellen. So erhalten Sie hochpräzise Ergebnisse. Möchten Sie berechnen, wie viel es kostet, einen Mann auf den Mars zu bringen und ihn wieder lebendig zu machen? Double reicht völlig aus.

Aber mit Geld gibt es oft sehr spezifische Regeln, die besagen, dass eine bestimmte Berechnung ein bestimmtes Ergebnis liefern muss und kein anderes. Wenn Sie einen Betrag berechnen, der sehr, sehr nahe an 98,135 USD liegt, gibt es häufig eine Regel, die bestimmt, ob das Ergebnis 98,14 USD oder 98,13 USD betragen soll. Sie müssen diese Regel befolgen und das erforderliche Ergebnis erhalten.

Je nachdem, wo Sie leben, funktioniert die Verwendung von 64-Bit-Ganzzahlen zur Darstellung von Cent, Pennies oder Kopeken oder der kleinsten Einheit in Ihrem Land normalerweise einwandfrei. Beispielsweise können 64-Bit-Ganzzahlen mit Vorzeichen, die Cent darstellen, Werte bis zu 92.223 Billionen Dollar darstellen. 32-Bit-Ganzzahlen sind normalerweise ungeeignet.

gnasher729
quelle
0

Nein, ein Double hat immer Rundungsfehler. Verwenden Sie "Dezimal", wenn Sie auf .Net ...

Thomas Hansen
quelle
6
Vorsichtig. Jede Gleitkomma-Darstellung weist Rundungsfehler auf, einschließlich Dezimalstellen. Es ist nur so, dass Dezimalstellen auf eine Weise gerundet werden, die für Menschen intuitiv ist (und im Allgemeinen für Geld geeignet ist), und binäre Gleitkommazahlen nicht. Aber für nichtfinanzielle Zahlenverarbeitung ist Double oft viel, viel besser als Dezimalzahl, selbst in C #.
Daniel Pryden
-4

Tatsächlich ist Gleitkomma- Double perfekt geeignet, um Geldbeträge darzustellen, solange Sie eine geeignete Einheit auswählen.

Siehe http://www.idinews.com/moneyRep.html

Ist also Fixpunkt lang . Entweder verbraucht es 8 Bytes, sicherlich besser als die 16, die von einem Dezimalelement verbraucht werden .

Ob etwas funktioniert oder nicht (dh das erwartete und korrekte Ergebnis liefert), ist weder eine Frage der Abstimmung noch der individuellen Präferenz. Eine Technik funktioniert entweder oder nicht.

Conrad Weisert
quelle
Wenn Sie einen Artikel verlinken, den Sie geschrieben haben und der mit jahrzehntelangen gängigen Praktiken und Expertenoptionen nicht einverstanden ist, dass Gleitkomma für die Darstellung von Finanztransaktionen ungeeignet ist, muss etwas mehr Backup als eine einzelne Seite vorhanden sein.
MuertoExcobito