2010-07-28 6 views
19

Mögliche Duplizieren:
problem in comparing double values in C#Warum hört diese Schleife nie auf?

Ich habe es an anderer Stelle gelesen, aber wirklich die Antwort vergessen, damit ich hier wieder fragen. Diese Schleife scheint nie unabhängig beenden Sie es in jeder Sprache kodieren (Ich teste es in C#, C++, Java ...):

double d = 2.0; 
while(d != 0.0){ 
    d = d - 0.2; 
} 
+2

Verwenden Sie niemals '==' mit Gleitkommawerten. Verwenden Sie vielleicht etwas wie "f> epsilon". – pascal

+20

[Es war ein paar Tage, ich denke, wir waren überfällig.] (Http://docs.sun.com/source/806-3568/ncg_goldberg.html) – GManNickG

+0

In Java, denke ich, gibt es die "Strictfp" Modifikator für solche Situationen –

Antwort

32

Gleitkomma-Berechnungen sind nicht vollkommen präzise. Sie erhalten einen Darstellungsfehler, weil 0.2 keine exakte Darstellung als binäre Fließkommazahl hat, so dass der Wert nicht genau gleich Null ist. Versuchen Sie, eine Debug-Anweisung hinzugefügt, das Problem zu sehen:

double d = 2.0; 
while (d != 0.0) 
{ 
    Console.WriteLine(d); 
    d = d - 0.2; 
} 
 
2 
1,8 
1,6 
1,4 
1,2 
1 
0,8 
0,6 
0,4 
0,2 
2,77555756156289E-16 // Not exactly zero!! 
-0,2 
-0,4 

Ein Weg, es zu lösen, ist die Art decimal zu verwenden.

+1

Und warum gibt es Rundfehler? Type 'double' speichert binäre Ziffern. Da die geschriebene binäre Zahl 0,2 ein periodischer Bruch ist, müssen wir etwas abschneiden, um die Zahl auf N Bits zu schreiben. – adf88

27

(Für eine Sache Sie die gleiche Variable nicht überall, aber ich werde davon ausgehen, dass ein Tippfehler :)

0.2 ist nicht wirklich 0,2. Es ist der nächste double Wert von 0,2. Wenn Sie das 10 Mal von 2.0 subtrahiert haben, werden Sie nicht mit genau 0.0 enden.

In C# können Sie ändern, anstatt den decimal Typ zu verwenden, die funktioniert:

// Works 
decimal d = 2.0m; 
while (d != 0.0m) { 
    d = d - 0.2m; 
} 

tut repräsentieren Dezimalzahlen wie 0.2 genau (innerhalb gewisser Grenzen Dies funktioniert, weil das Dezimalsystem Typ, es ist ein 128- ist Bit-Typ). Jeder Wert ist genau darstellbar, also funktioniert es. Welche würde nicht Arbeit sein würde:

decimal d = 2.0m; 
while (d != 0.0m) { 
    d = d - 1m/3m; 
} 

Hier „ein dritte“ ist nicht genau darstellbare so enden wir nach wie vor mit dem gleichen Problem auf.

Im Allgemeinen ist es jedoch eine schlechte Idee, exakte Gleichheitsvergleiche zwischen Fließkommazahlen durchzuführen - normalerweise vergleicht man sie innerhalb einer bestimmten Toleranz.

Ich habe Artikel über floating binary point und floating decimal point aus einem C# /. NET-Kontext, die Dinge im Detail erklären.

1

f uninitialised;)

Wenn Sie nach:

double f = 2.0; 

Dies kann eine Wirkung von nicht-präziser arthimetic auf Double-Variablen sein.

3

Sie sind besser dran

while(f > 0.0) 

* edit mit: pascal Kommentar Siehe unten. Aber wenn Sie eine Schleife eine integrale, deterministische Anzahl von Malen ausführen müssen, verwenden Sie lieber einen ganzzahligen Datentyp.

+0

Ich weiß, ich sollte es verwenden, aber warum f! = 0.0 funktioniert nicht? –

+1

Sie könnten einmal zu oft laufen, wenn das letzte 'f' 2.0e-16 ist ... – pascal

1

ist es wegen der Präzision der Fließkommazahl. verwenden Sie while (d> 0.0), oder wenn Sie müssen,

while (Math.abs(d-0.0) > some_small_value){ 

} 
2

Das Problem ist Gleitkommaarithmetik. Wenn es für eine Zahl keine genaue Binärdarstellung gibt, können Sie nur die nächste Nummer speichern (so wie Sie die Zahl 1/3 nicht im Dezimalformat speichern konnten - Sie können nur etwas wie 0.33333333 für eine Länge von '3 speichern.) Dies bedeutet, dass die arithmetische Berechnung von Gleitkommazahlen häufig nicht genau ist. Versuchen Sie so etwas wie die folgenden (Java):

public class Looping { 

    public static void main(String[] args) { 

     double d = 2.0; 
     while(d != 0.0 && d >= 0.0) { 
      System.out.println(d); 
      d = d - 0.2; 
     } 

    } 

} 

Ihr Ausgang etwas sein sollte:

2.0 
1.8 
1.6 
1.4000000000000001 
1.2000000000000002 
1.0000000000000002 
0.8000000000000003 
0.6000000000000003 
0.4000000000000003 
0.2000000000000003 
2.7755575615628914E-16 

Und jetzt sollten Sie in der Lage sein, zu sehen, warum die Bedingung d == 0 nie passiert. . (Die letzte Zahl ist eine Zahl, die sehr nahe 0 ist aber nicht ganz

Ein weiteres Beispiel für Punkt Seltsamkeit schwimmen, versuchen Sie dies:

public class Squaring{ 

    public static void main(String[] args) { 

     double d = 0.1; 
     System.out.println(d*d); 

    } 

} 

Da es keine binär Darstellung genau 0.1, quadriert es das Ergebnis nicht produzieren Sie es erwarten würden (0.01), aber tatsächlich so etwas wie 0.010000000000000002!

+1

" Never total exact "übertreibt es, IMO. Nur weil nicht * jeder * Dezimalwert im binären Fließkomma nicht exakt darstellbar ist, macht die Addition der genau dargestellten 0,25 und 0,25 nicht genau genau so viel genauer. –

+0

Bearbeitet, danke Jon - es war etwas übertrieben. – Stephen

10

ich erinnere mich, den Kauf eines Sinclair ZX-81, meinen Weg durch die hervorragende Grundprogrammierhandbuch zu arbeiten, und fast Rückkehr in den Laden, als ich auf meinen ersten Gleitkomma-Rundungsfehler stieß.

Ich hätte mir nie vorstellen können, dass Menschen diese Probleme noch 27.99998 Jahre später haben.

+2

Das ZX-Spectrum-Handbuch wurde mit einer ausführlichen Erläuterung dieses Problems geliefert. Ich kann mich erinnern, dass ich darüber nachgedacht habe (ungefähr im Alter von 10 oder 11) und "Oh, das macht Sinn". Es war ein sehr gutes Handbuch ... –

+7

+1 für 27.99998 Jahre :-) –

0

Es hört nicht auf, weil 0,2 nicht genau in Zweierkomplement dargestellt i so Ihre Schleife führt nie den 0.0==0.0 Test

+0

Zweierkomplement hat keine Verbindung mit Rundungsfehlern von Fließkommazahlen. – Egon

0

Wie andere gesagt haben, ist dies nur ein grundsätzliches Problem, das Sie erhalten, wenn Gleitkommazahlen tun Arithmetik zu beliebig Basis. Es kommt einfach vor, dass Base-2 am häufigsten in Computern verwendet wird (weil es eine effiziente Hardware-Implementierung zulässt).

Die beste Lösung, wenn möglich, besteht darin, auf eine Art Quotientendarstellung der Zahl für Ihre Schleife umzuschalten, wodurch der Gleitkommawert daraus abgeleitet wird. OK, das klingt übertrieben! Für Ihren speziellen Fall würde ich es schreiben, wie:

int dTimes10 = 20; 
double d; 
while(dTimes10 != 0) { 
    dTimes10 -= 2; 
    d = dTimes10/10.0; 
} 

Hier sind wir wirklich mit Brüchen arbeiten [20/10, 18/10, 16/10, ..., 10.02, 0/10] wobei die Iteration mit Ganzzahlen (dh leicht zu korrigieren) im Zähler mit einem festen Nenner durchgeführt wird, bevor sie in Gleitkommawerte umgewandelt werden. Wenn Sie Ihre echte Iterationen umschreiben können, um so zu arbeiten, werden Sie großen Erfolg haben (und sie sind wirklich nicht viel teurer als das, was Sie vorher überhaupt taten, was ein großer Kompromiss ist, um Korrektheit zu erhalten).

Wenn Sie dies nicht tun können, müssen Sie equal-in-epsilon als Vergleich verwenden. Ungefähr, das ersetzt d != target durch abs(d - target) < ε, wo ε (epsilon) Auswahl manchmal peinlich sein kann. Grundsätzlich hängt der richtige Wert von ε von einer Reihe von Faktoren ab, aber es ist wahrscheinlich am besten als 0 ausgewählt.001 für die beispielhafte Iteration, gegeben durch die Skalierung des Schrittwerts (d. H. Es ist ein halbes Prozent der Größe des Schritts, so dass alles in diesem Fehler statt informativ sein wird).

Verwandte Themen