2009-07-09 12 views
2

Ein Fehler, den ich auf alle paar Monate stolpern ist diese:Ist der dotNet-Dezimaltyp anfällig für den binären Vergleichsfehler?

 double x = 19.08; 
     double y = 2.01; 
     double result = 21.09; 

     if (x + y == result) 
     { 
      MessageBox.Show("x equals y"); 
     } 
     else 
     { 
      MessageBox.Show("that shouldn't happen!"); // <-- this code fires 
     } 

Sie den Code annehmen würde, um „x gleich y“, aber das ist nicht der Fall.
Die kurze Erklärung ist, dass die Dezimalstellen, dargestellt als eine Binärziffer, nicht in das Doppelte passen.

Beispiel: 2.625 würde wie folgt aussehen:

10,101

weil

1-------0-------1---------0----------1 
1 * 2 + 0 * 1 + 1 * 0.5 + 0 * 0.25 + 1 * 0,125 = 2.65 

Und einige Werte (wie das Ergebnis von 19,08 und 2,01) nicht mit den Bits a dargestellt werden, werden doppelt.

Eine Lösung ist eine Konstante zu verwenden:

 double x = 19.08; 
     double y = 2.01; 
     double result = 21.09; 
     double EPSILON = 10E-10; 

     if (x + y - result < EPSILON) 
     { 
      MessageBox.Show("x equals y"); // <-- this code fires 
     } 
     else 
     { 
      MessageBox.Show("that shouldn't happen!"); 
     } 

Wenn ich dezimal anstelle von Doppel im ersten Beispiel, ist das Ergebnis „x y gleich“.
Aber ich frage mich, ob dies wegen "dezimal" Typ ist nicht anfällig für dieses Verhalten oder es funktioniert einfach in diesem Fall, weil die Werte in 128 Bit "passen".

Vielleicht hat jemand eine bessere Lösung als die Verwendung einer Konstante?

Btw. Dies ist kein dotNet/C# -Problem, es passiert in den meisten Programmiersprachen, denke ich.

+0

Es ist sicher http://docs.sun.com/source/806-3568/ncg_goldberg.html ist ein berühmtes Papier. – RichardOD

Antwort

6

Dezimal wird genau sein, solange Sie innerhalb von Werten bleiben, die natürlich Dezimalzahlen in einem geeigneten Bereich sind. Wenn Sie also beispielsweise nur addieren und subtrahieren, ohne etwas zu tun, was den Bereich der zu großen Ziffern verzerrt (Hinzufügen einer sehr sehr großen Zahl zu einer sehr kleinen Zahl), werden Sie leicht vergleichbare Ergebnisse erhalten. Vermehrung ist wahrscheinlich auch in Ordnung, aber ich vermute, dass es einfacher ist, Ungenauigkeiten damit zu bekommen.

Sobald Sie anfangen, Dividieren, das ist, wo die Probleme kommen können - vor allem, wenn Sie durch Zahlen Dividieren beginnen, die anderen Primfaktoren sind als 2 oder 5

Fazit: es ist sicher in bestimmten Situationen, aber Sie Sie müssen genau wissen, welche Operationen Sie ausführen werden.

Beachten Sie, dass es nicht die 128-Bitness der Nachkommastellen, die Sie hier hilft - es ist die Darstellung von Zahlen ist, als dezimal Gleitkommazahlen statt Gleitkomma binären Punktwerte. Weitere Informationen finden Sie in meinen Artikeln zu .NET binary floating point und .

+0

Danke, aber das Problem tritt nicht nur beim Teilen auf. Während der Transformation dezimal -> binär -> dezimal kann es vorkommen, dass eine Dezimalstelle mit 2 Dezimalstellen in eine große (gerade Infinitiv?) Anzahl binärer "dezimaler" Stellen umgewandelt wird, die bei einem größeren Betrag als 128 Bit verworfen werden. Wenn Sie diese "gerundete" Binärzahl zurück in Dezimal konvertieren, haben Sie ein anderes Ergebnis. –

+0

@SchlaWiener: Wenn Sie zwischen Doppel und Dezimal konvertieren, haben Sie sicherlich ein Problem. Tu das nicht. Bleiben Sie bei dem einen oder anderen Format. –

+0

Ich möchte nicht konvertieren. In Bezug auf Ihr Artikel "Dezimal Gleitkomma" erwähnen Sie, dass die Dezimalstelle "10" basiert. Bedeutet das, dass a + b = c immer wahr zurückgibt, wenn ein Plus b wirklich c ist? –

0

System.Decimal ist nur eine Gleitkommazahl mit einer anderen Basis, also ist es in der Theorie immer noch anfällig für die Art von Fehler, auf den Sie hinweisen. Ich glaube, Sie sind gerade auf einen Fall gestoßen, bei dem keine Rundung stattfindet. Weitere Informationen here.

+0

Tatsächlich sind die meisten Beträge, mit denen Leute umgehen, Basis 10 und der Dezimaltyp kann diese Beträge genau darstellen (innerhalb der Genauigkeitsgrenzen) .Deshalb ist der Dezimaltyp ideal für den realen Gebrauch, wie etwa den Umgang mit Geldsummen. – redcalx

0

Ja, die .NET System.Double-Struktur unterliegt dem von Ihnen beschriebenen Problem.

von http://msdn.microsoft.com/en-us/library/system.double.epsilon.aspx:

Zwei scheinbar äquivalent Gleitkommazahlen möglicherweise nicht wegen der Unterschiede in ihren letzten signifikanten Stellen gleich vergleichen. Zum Beispiel vergleicht der C# -Ausdruck (doppelt) 1/3 == (doppelt) 0.33333 nicht gleich, weil die Divisionsoperation auf der linken Seite maximale Genauigkeit hat, während die Konstante auf der rechten Seite nur für die spezifizierten Ziffern präzise ist. Wenn Sie einen benutzerdefinierten Algorithmus erstellen, der bestimmt, ob zwei Gleitpunktzahlen als gleichwertig betrachtet werden können, müssen Sie einen Wert verwenden, der größer als die Epsilon-Konstante ist, um den zulässigen absoluten Differenzabstand für die beiden Werte als gleich zu definieren. (In der Regel ist diese Differenzspanne um ein Vielfaches größer als Epsilon.)

Verwandte Themen