2013-02-13 7 views
6

ich eine Schleife schreibe, die mit einem Schwimmer erhöht, aber ich habe über ein Gleitkommaarithmetik Problem dargestellt in dem folgende Beispiel kommen:C++ Wie Gleitkommaarithmetik Fehler zu vermeiden

for(float value = -2.0; value <= 2.0; value += 0.2) 
    std::cout << value << std::endl; 

Hier ist die Ausgang:

-2 
-1.8 
-1.6 
-1.4 
-1.2 
-1 
-0.8 
-0.6 
-0.4 
-0.2 
1.46031e-07 
0.2 
0.4 
0.6 
0.8 
1 
1.2 
1.4 
1.6 
1.8 

Warum genau bin ich 1.46031e-07 statt 0 bekommen? Ich weiß, dass dies etwas mit Fließkommafehlern zu tun hat, aber ich kann nicht verstehen, warum es passiert und was ich tun sollte, um dies zu verhindern (wenn es einen Weg gibt). Kann mir jemand erklären (oder auf einen Link verweisen), der mir hilft zu verstehen? Jede Eingabe wird geschätzt. Vielen Dank!

+4

"Wie Gleitkommaarithmetik Fehler zu vermeiden" - Sie können nicht, sorry. oft –

+0

dies wurde. Sie können dies sehen, wenn Sie die Genauigkeit der Ausgabe erhöhen: [Beispiel] (http://liveworkspace.org/code/3ZXIxx$0). – bernie

+3

'0.2' gestellt und beantwortet nicht exakt durch einen' float' dargestellt (unter der Annahme IEEE754 Gleitpunktarithmetik) – Mankarse

Antwort

7

Dies liegt daran, dass Fließkommazahlen nur eine bestimmte diskrete Genauigkeit haben.

Die 0,2 ist nicht wirklich eine 0,2, sondern wird intern als etwas andere Zahl dargestellt.

Deshalb sehen Sie einen Unterschied.

Dies ist in allen Gleitkommaberechnungen üblich, und Sie können es wirklich nicht vermeiden.

+1

Es ist wichtig darauf hinzuweisen, dass, während 0.2 nicht genau als ein Float dargestellt werden kann, -2.0 und 2.0. Ich weise darauf hin, um den Eindruck zu vermeiden, dass Fließkommamathematik willkürlich und launisch ist. Alles geschieht, ist, dass Schwimmer und Doppelnutzung Basis 2 und 0,2 ist äquivalent zu 1/5, die nicht als eine finite Basis 2 Zahl dargestellt werden kann. -2, 2,0, 0,5, 0,25, -.375 und 178432 können alle genau dargestellt werden. –

1

Erfahren Sie mehr über Gleitkommadarstellung mit einigen Algorithmen Buch oder mit Internet. Es gibt viele Ressourcen da draußen.

Für die Zeit, was Sie wollen, scheint ein Weg, um Null zu sein, wenn es etwas sehr nahe Null ist. und wir alle wissen, dass wir diesen Prozess "Rundung" nennen. :) Warum benutzt du es nicht beim Drucken dieser Zahlen? printf Funktion bietet eine gute Formatierungsleistung für diese Art von Dingen. Überprüfen Sie die Tabellen im folgenden Link, wenn Sie nicht wissen, wie man mit printf formatiert. (Sie können die Formatierung verwenden zum Runden und Anzeigen der Zahlen korrekt) printf ref: http://www.cplusplus.com/reference/cstdio/printf/?kw=printf

- bearbeiten -

vielleicht einige von euch wissen, dass nach Mathematik 1,99999999 .... ist die gleiche als 2.0. Einziger Unterschied ist die Darstellung. Aber die Nummer ist dieselbe.

Ihr Gleitkomma Problem ist ein kleines Bit ähnlich zu diesem. (Dies ist nur für Ihre Klarstellung nur Ihr Problem ist nicht das gleiche wie die 1,9999 .... Sache..)

5

Verwenden ganze Zahlen und herunterzuteilen:

for(int value = -20; value <= 20; value += 2) 
    std::cout << (value/10.0) << std::endl; 
+0

+1 enthalten, aber ... durch Dividieren 'value' von' 10.0', Sie schlagen vor, an den Compiler, dass es mit doppelter Genauigkeit berechnen sollte, und dann in einfacher Genauigkeit konvertieren (das Programm, das Sie die OP versuchen, hat eine Single-Precision-Variable zu emulieren). Es ergibt sich so, dass dies das gleiche Ergebnis wie eine gerade Einprägung ergibt. Da der Grund dafür, dass es identische Ergebnisse liefert, nicht trivial ist, wird der Compiler höchstwahrscheinlich Code für eine Division mit doppelter Genauigkeit erzeugen, gefolgt von der Umwandlung von Double in Single-Precision. Aus diesem Grund wäre "value/10.0f" marginal besser. –

+0

Ich habe nur überprüft und GCC erzeugt eine einfache Genauigkeit Division für 'float r = f/10.0;'. Ich bin beeindruckt (und mein vorheriger Kommentar verliert viel von seinem Wert). –

23

Wie alle andere gesagt hat, das ist tue zu der Tatsache, dass die reellen Zahlen eine unendliche und unzählbare Menge sind, während Fließkommadarstellungen eine endliche Anzahl von Bits verwenden. Fließkommazahlen können nur reellen Zahlen angenähert werden und sind in vielen einfachen Fällen aufgrund ihrer Definition nicht präzise. Wie Sie jetzt gesehen haben, ist 0.2 nicht wirklich 0.2, sondern ist stattdessen eine Nummer sehr nah dran. Wenn Sie diese zu value hinzufügen, akkumulieren Sie den Fehler bei jedem Schritt.

Als Alternative versuchen int s für Ihre Iteration und Dividieren des Ergebnisses in der Domain, die Sie benötigen, um wieder:

for (int value = -20; value <= 20; value += 2) { 
    std::cout << (value/10.f) << std::endl; 
} 

Für mich ergibt dies:

-2 
-1.8 
-1.6 
-1.4 
-1.2 
-1 
-0.8 
-0.6 
-0.4 
-0.2 
0 
0.2 
0.4 
0.6 
0.8 
1 
1.2 
1.4 
1.6 
1.8 
2 
+2

Ich kann nicht glauben, dass dies nicht die akzeptierte Antwort ist, da diese und eine andere Antwort die einzigen sind, die eine Lösung geben. – Celeritas

+0

Es erinnert mich die berühmten Probleme im Zusammenhang mit [ULPs] (http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition) –

5

Lasst uns tun Ihre Schleife, aber mit erhöhter Ausgabegenauigkeit.

Code:

for(float value = -2.0; value <= 2.0; value += 0.2) 
    std::cout << std::setprecision(100) << value << std::endl; 

Ausgang:

-2 
-1.7999999523162841796875 
-1.599999904632568359375 
-1.3999998569488525390625 
-1.19999980926513671875 
-0.999999821186065673828125 
-0.79999983310699462890625 
-0.599999845027923583984375 
-0.3999998569488525390625 
-0.19999985396862030029296875 
1.460313825418779742904007434844970703125e-07 
0.20000015199184417724609375 
0.400000154972076416015625 
0.6000001430511474609375 
0.800000131130218505859375 
1.00000011920928955078125 
1.20000016689300537109375 
1.40000021457672119140625 
1.60000026226043701171875 
1.80000030994415283203125