2013-05-18 5 views
6

Math.Round(8.075, 2, MidpointRounding.AwayFromZero) kehrt 8.07, obwohl es 8.08 zurückkehren. Mysteriously genug, 7.075 funktioniert gut, aber 9.075 gibt auch 9.07!Math.round Bug - was tun?

Was ist zu tun? Kennt jemand eine Rundungsmethode ohne solche Fehler?

+2

Ich bin sehr zweifelhaft .net solche Fehler haben würde, wenn es das ist, was passiert ist, dann wurde ich bin sicher, dass es auf diese Weise aus einem Grunde implementiert. –

+2

Sie können einen Dezimaltyp verwenden, da sie präziser sind: 'Math.Round (8.075m, 2, MidpointRounding.AwayFromZero) ' –

+0

Wir brauchen Jon Skeet hier – VladL

Antwort

4

Ich bin kein .net-Spezialist, aber diese Zahlen nicht so exakt doppelt dargestellt werden, so dass die Rundung ist genau, wenn Sie berücksichtigen, den realen Wert dieser drei Zahlen:

7.075 ==> 7.07500000000000017763568394002504646778106689453125 
8.075 ==> 8.074999999999999289457264239899814128875732421875 
9.075 ==> 9.074999999999999289457264239899814128875732421875 

Mehr über Gleitkommapräzision: What Every Computer Scientist Should Know About Floating-Point Arithmetic.

+0

Wie haben Sie die wahren Werte gefunden? –

+0

@AshBurlaczenko Mit einem Java-Programm, aber ich nehme an, Sie können das gleiche in .NET tun. – assylias

+0

@AshBurlaczenko: Ich habe ein C# -Programm dafür: http://blogs.msdn.com/b/ericlippert/archive/2011/02/17/looking-inside-a-double.aspx und eine aktuelle Follow-up post hier: http://ericlippert.com/2013/05/13/spot-the-defect-rounding/ –

9

Wenn Sie mit 10 Fingern abzählen, wie Menschen es tun, Sie haben keine Probleme den Dezimalwert exprimierenden 8.075 genau:

8.075 = 8 x 10^1 + 0 x 10^0 + 7 x 10^-1 + 5 x 10^-2 

Aber Computer mit zwei Fingern abzählen, sie brauchen diesen Wert in auszudrücken Potenzen von 2:

8.075 = 1 x 2^3 + 0 x 2^2 + 0 x 2^1 + 0 x 2^0 + 0 x 2^-1 + 0 x 2^-2 + 0 x 2^-3 + 
      1 x 2^-4 + 0 x 2^-5 + 0 x 2^-6 + 1 x 2^-7 + 1 x 2^-8 + 0 x 2^-9 + 0 x 2^-10 + 
      1 x 2^-11 + ... 

gab ich die Bedingungen mit dem Finger krampf eingeben, aber der Punkt ist, dass, egal wie viele Potenzen von 2 Sie hinzufügen, werden Sie nie genau 8.075m bekommen. Ein ähnliches Problem, wie Menschen niemals das Ergebnis von 10/3 genau schreiben können, hat eine unendliche Anzahl von Ziffern in der Fraktion. Sie können das Ergebnis dieses Ausdrucks nur dann genau schreiben, wenn Sie mit 6 Fingern zählen.

Ein Prozessor natürlich nicht über genügend Speicher eine unendliche Anzahl von Bits speichern einen Wert darzustellen. So müssen sie gestutzt die Ziffernfolge, einen Wert vom Typ Doppel kann 53 Bits speichern.

Als Ergebnis der Dezimalwert 8.075 gerundet wird, wenn es in dem Prozessor gespeichert ist. Die Folge von 53 Bits, zurück in Dezimal konvertiert, ist der Wert ~ 8.074999999999999289. Was dann wie erwartet von Ihrem Code auf 8,07 gerundet wird.

Wenn Sie 10 Finger Mathe Ergebnisse wollen, müssen Sie einen Datentyp verwenden, die Zahlen in der Basis speichert 10. Das ist der System.Decimal Typ in .NET ist. Fix:

decimal result = Math.Round(8.075m, 2, MidpointRounding.AwayFromZero) 

Beachten Sie die Verwendung des Buchstaben m in den 8.075m wörtlichen im Snippet, ein wörtlichen vom Typ dezimal. Wählt die Math.Round() - Überladung aus, die mit 10 Fingern zählt, zuvor haben Sie die Überladung verwendet, die System.Double, die 2-Finger-Version, verwendet.

Sie beachten Sie, dass ein erheblicher Nachteil ist mit System.Decimal zu berechnen, ist es langsam. Viel, viel langsamer als mit System.Double, einem Werttyp, der direkt vom Prozessor unterstützt wird. Dezimalmathematik wird in Software ausgeführt und ist nicht hardwarebeschleunigt.

+1

Deshalb * real * Mathematiker [zählen mit 60 Fingern] (http://en.wikipedia.org/wiki/Sexagesimal). – svick

-2

könnte die mögliche Lösung:

(double)Math.Round((decimal)8.075, 2, MidpointRounding.AwayFromZero);