2015-08-25 7 views
58

Gestern während seltsame etwas Debuggen mir passiert ist, und ich kann es nicht wirklich erklären:Unrichtigkeit von dezimal in .NET

Decimal calculation

Decimal calculations with brackets

Vielleicht bin ich nicht das Offensichtliche hier zu sehen oder Ich habe etwas über Dezimalstellen in .NET falsch verstanden, aber sollten die Ergebnisse nicht die gleichen sein?

+3

Sie können die [Floating-Point-Problem] (http://stackoverflow.com/questions/1584314/conversion-of-a-decimal-to-double-number-in-c-sharp-results-in-a- -unterschied) – Khatibzadeh

+0

Related: http://stackoverflow.com/q/618535/1394393. Nicht ein Betrogener. (Zumindest greift dies auf einen Aspekt zurück, der häufig missverstanden wird.) – jpmc26

Antwort

66

decimal ist keine magische machen Sie die ganze Mathematik für mich Typ. Es ist immer noch eine Gleitkommazahl - der Hauptunterschied von float ist, dass es eine dezimale Fließkommazahl ist, anstatt binär. So können Sie einfach 0.3 als Dezimalzahl darstellen (das ist als endliche Binärzahl unmöglich), aber Sie haben keine unendliche Genauigkeit.

Das macht es viel näher an einem Menschen, der die gleichen Berechnungen macht, aber Sie müssen sich immer noch vorstellen, dass jemand jede Operation einzeln durchführt. Es wurde speziell für Finanzberechnungen entwickelt, bei denen Sie nicht so tun, wie Sie es in Mathe tun - Sie gehen einfach Schritt für Schritt und runden jedes Ergebnis nach ziemlich spezifischen Regeln ab.

In der Tat könnte decimal in vielen Fällen viel schlechter als float (oder besser, double) arbeiten. Dies liegt daran, dass decimal keine automatische Rundung durchführt. Das Gleiche mit double gibt 22 wie erwartet, weil automatisch angenommen wird, dass der Unterschied keine Rolle spielt - in decimal, es tut - das ist einer der wichtigen Punkte über decimal. Sie können dies emulieren, indem Sie manuell Math.Round s eingeben, aber es macht nicht viel Sinn.

14

Durch Hinzufügen von Klammern stellen Sie sicher, dass die Division vor der Multiplikation berechnet wird. Dies scheint subtil genug zu sein, um die Berechnung ausreichend zu beeinflussen, um eine floating precision issue einzuführen.

Da Computer nicht tatsächlich jede mögliche Zahl produzieren, sollten Sie sicherstellen, dass Sie diese

+1

Als kleine Anmerkung kann der Mensch auch nicht jede mögliche Zahl produzieren. '1/3' ist ein großartiges Beispiel dafür, denn jeder wird entscheiden, welche Präzision für sie genug ist und niemals versuchen herauszufinden, ob es jemals eine Lösung gibt. (Spoiler: da ist nicht. Es ist immer rekursive 3) – Sayse

+6

Das coole Ding, das Menschen tun können, ist es, behalten Sie es als '1/3 'für den Rest der Berechnung, oder schreiben Sie' 0.33 '(unendliche Expansion). Selbst für irrationale Zahlen können wir schreiben, wie sie definiert sind, oder einfach eine Konstante verwenden. Ziemlich handlich, wenn Sie die Präzision wirklich ernst nehmen: D – Luaan

+0

Diese Antwort erklärt die Ursache sehr gut. Wenn Sie mit Zahlen mit hoher Genauigkeit arbeiten müssen, verwenden Sie besser den BigDecimal-Datentyp. – Hamed

29

Dezimal kann nur speichern, genau Werte in Ihre Berechnungen Faktor, der dezimal exakt darstellbar sind. Hier 22/24 = 0.91666666666666666666666 ..., die unendliche Genauigkeit oder einen rationalen Typ zum Speichern benötigt, und es entspricht nicht mehr 22/24 nach dem Runden. Wenn Sie zuerst die Multiplikation durchführen, sind alle Werte genau darstellbar, also das Ergebnis, das Sie sehen.

1

Während Decimal eine höhere Präzision als Double hat, sein primäres nützliches Feature ist, dass jeder Wert genau seine menschenlesbare Darstellung entspricht. Während die in einigen Sprachen verfügbaren Fest-Dezimal-Typen garantieren können, dass weder das Addieren oder Subtrahieren von zwei Festkommazahlen mit übereinstimmender Genauigkeit noch die Multiplikation eines Festkommatyps mit einer Ganzzahl zu Rundungsfehlern führt, und während " "Big-Decimal" -Typen, wie sie in Java vorkommen, können garantieren, dass keine Multiplikation Rundungsfehler verursacht, Gleitkommazahlen wie der .NET-Typ bieten keine solchen Garantien, und keine Dezimalzahlen können garantieren, dass Divisionsoperationen möglich sind abgeschlossen ohne Rundungsfehler (Java hat die Möglichkeit, eine Ausnahme auszulösen, falls eine Rundung notwendig wäre).

Während diejenigen, die sich dafür entscheiden, Decimal zu einem Gleitkommatyp zu machen, können sie entweder für Situationen geeignet sein, in denen mehr Ziffern rechts vom Komma benötigt werden, oder mehr links, Fließkommatypen, ob Basis- 10 oder base-2, machen Rundungsprobleme unvermeidlich für alle Operationen.