PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [C++] 11.00 == 11.00 => false ???


Pompos
2008-11-09, 18:16:57
Hej....

ich habe ein kleines Problem mit einer While-Schleife :(

Der letzte Durchlauf klappt einfach nicht, weil es irgendeinen Unterschied zwischen 11 und 11 gibt. Leider habe ich keine Ahnung welcher dass ist und wie ich das Problem beheben kann. Wäre um Hilfe sehr dankbar.

Hier die simplifizierte Version:

float tmpStart = 0;
float tmpStop = 11;
float tmpStep = 1.1;

while (tmpStart <= tmpStop) {
cout << "start: " << tmpStart << " | stop: " << tmpStop << endl;
tmpStart = tmpStart + tmpStep;
if (tmpStart == tmpStop) { cout << "gleich" << endl;}
}

SGT.Hawk
2008-11-09, 18:30:20
Ganz einfach, Fliesskommazahlen prüft man nicht auf Gleichheit.

Sephiroth
2008-11-09, 18:49:50
Und die 0,1 kann als Binärzahl eh nicht präzise dargestellt werden (unendliche periodische Dualzahl). Noch ein Grund wieso es scheitert.
Was man machen kann, ist es den Abstand zweier Floats zu prüfen und zu sagen: ok, wenn der Abstand kleiner einem sehr kleinen Epsilon ist, dann seien sie gleich.

if (fabs(result - expectedResult) < 0.00001)

Comparing floating point numbers (http://www.cygnus-software.com/papers/comparingfloats/Comparing%20floating%20point%20numbers.htm)

Shink
2008-11-09, 18:59:35
Der letzte Durchlauf klappt einfach nicht, weil es irgendeinen Unterschied zwischen 11 und 11 gibt. Leider habe ich keine Ahnung welcher dass ist und wie ich das Problem beheben kann. Wäre um Hilfe sehr dankbar.
Schön dass du da jetzt schon draufkommst:tongue:

11 in Fließkomma ist nunmal nicht 11 sondern als (s·m·2^e). Und da eine CPU nunmal nicht unendlich genau rechnet ist z.B. 10+1 in dieser Abbildungsweise garantiert ab irgendeiner Kommastelle etwas anderes als 11.

Lösungen:
- "Fixkomma"format o.ä. verwenden
- Schwellenwert s einführen (statt if a==b verwendet man if |a-b|<s). Was s ist hängt von der Anwendung ab oder kann/muss/soll aufgrund der Eingangsdaten berechnet werden.

Gnafoo
2008-11-09, 19:00:58
Anstatt die Fließkommazahlen aufzuaddieren empfiehlt es sich auch oft, mit Integern zu zählen und diese mit einer Fließkommazahl zu multiplizieren. Nach folgendem Schema:


float step = 1.1;

for (int i = 0; i < 10; i++)
{
cout << "count: " << (i * step) << endl;
}


Dann kann sich der Fehler wenigstens nicht aufaddieren. Das setzt allerdings voraus, dass man die Anzahl der Schritte im Vorneherein bestimmen kann.

Trap
2008-11-09, 19:06:28
Natürlich kann man Fließkommazahlen addieren und auch auf Gleichheit prüfen, wenn man sie richtig wählt. Nämlich so, dass alle Zwischenergebnisse exakt repräsentierbar sind.

Monger
2008-11-09, 19:13:59
Natürlich kann man Fließkommazahlen addieren und auch auf Gleichheit prüfen, wenn man sie richtig wählt. Nämlich so, dass alle Zwischenergebnisse exakt repräsentierbar sind.
Ich glaube nicht, dass es irgendeinen Menschen gibt der mit dem Gleitkommazahlensystem intuitiv umgehen kann! ;)
Iterieren über Gleitkommazahlen ist auf jeden Fall keine allzu tolle Idee.


Gleitkommazahlen sind nunmal für wissenschaftliche Anwendungen gedacht, um mit logarithmischen Zahlenbereichen umgehen zu können. Das dürfte für die allerwenigsten Entwickler von Bedeutung sein. Und nur um einen Punkt zwischen zwei Ziffern zu kriegen... dafür gibt es bessere Wege! ;)

PatkIllA
2008-11-09, 19:14:36
Natürlich kann man Fließkommazahlen addieren und auch auf Gleichheit prüfen, wenn man sie richtig wählt. Nämlich so, dass alle Zwischenergebnisse exakt repräsentierbar sind.
Wie oft ist das denn der Fall?

@shink
11 und die ganzen anderen Ganzzahlen bis zu einer gewissen Größe läßt sich problemlos bei den üblichen Formaten darstellen.

Trap
2008-11-09, 19:30:53
Ich glaube nicht, dass es irgendeinen Menschen gibt der mit dem Gleitkommazahlensystem intuitiv umgehen kann! ;)
Deshalb sollte man es auch garnicht versuchen, sondern sich einfach mal Hinsetzen und lernen wie die Operationen auf Gleitkommazahlen funktionieren.

Dann braucht man auch keine tollen Tipps wie "vergleiche grundsätzlich mit Toleranz", usw...

Über kleine Ganzzahlen iterieren kann man auch mit float als Datentyp. Das hat den Vorteil, dass nicht jedesmal konvertiert werden muss, wenn man in der Schleife mit rechnen möchte.
Und nur um einen Punkt zwischen zwei Ziffern zu kriegen... dafür gibt es bessere Wege! ;)
So ist es.
Wie oft ist das denn der Fall?
Bei Fließkommazahlen im Programmcode ist das kein Problem.

Pompos
2008-11-09, 19:52:18
merci leute...

Dass die Floats daran schuld sind, habe ich mir schon gedacht. Naja... habs nun so gelöst, dass ich schau, wie häufig tmpStep als Integer in (tmpStop - tmpStart) (stop ist immer größer als start; wird vorhergecheckt) passt. Das Ergebnis wird dann schön in einer For-Schleife verwendet :D

Shink
2008-11-09, 19:53:04
Natürlich kann man Fließkommazahlen addieren und auch auf Gleichheit prüfen, wenn man sie richtig wählt. Nämlich so, dass alle Zwischenergebnisse exakt repräsentierbar sind.
Anstatt die Fließkommazahlen aufzuaddieren empfiehlt es sich auch oft, mit Integern zu zählen und diese mit einer Fließkommazahl zu multiplizieren.
Und der der den Code mal liest und ändern will weiß auch ganz bestimmt was man da im Kopf hatte?

@shink
11 und die ganzen anderen Ganzzahlen bis zu einer gewissen Größe läßt sich problemlos bei den üblichen Formaten darstellen.
Und wenn er mal nicht mehr 11 verwendet?

@all: Davon abgesehen: Sind Fließkommazahlen nicht prinzipiell maschinenabhängig? Hat der Threadstarter gesagt wofür er programmiert?

Trap
2008-11-09, 20:03:46
Und der der den Code mal liest und ändern will weiß auch ganz bestimmt was man da im Kopf hatte?
Wenn man Leute ohne Verständnis von Fließkommazahlen an Programmcode mit Fließkommazahlen arbeiten lässt, hat man sowieso ein schwerwiegendes Problem.
@all: Davon abgesehen: Sind Fließkommazahlen nicht prinzipiell maschinenabhängig?
Es gibt IEEE754 Fließkommazahlen und exotisches Zeug was niemand verwendet. Alle aktuellen verbreiteten CPUs benutzen IEEE754.

pest
2008-11-09, 20:15:44
Fließkomma...Fließkomma...was zum Teufel ist Fließkomma

Shink
2008-11-09, 20:24:18
Es gibt IEEE754 Fließkommazahlen und exotisches Zeug was niemand verwendet. Alle aktuellen verbreiteten CPUs benutzen IEEE754.
Ausnahmen sind einige IBM-Großrechnersysteme, die VAX-Architektur und einige Supercomputer, etwa von Cray sowie die Java Virtual Machine
Ja, stimmt. Wer programmiert schon für die Java Virtual Machine.:biggrin:
Übrigens hab ich an meinem vorigen Arbeitsplatz zu einem Drittel für IBM-Großrechnersysteme entwickelt.
Und was ist mit zukünftigen CPUs?

Wenn man Leute ohne Verständnis von Fließkommazahlen an Programmcode mit Fließkommazahlen arbeiten lässt, hat man sowieso ein schwerwiegendes Problem.
Wie man sieht wissen sehr viele Leute gar nicht dass sie (zu) wenig über Fließkommazahlen wissen. Vielleicht vermuten sie auch nichts böses...

@pest: Ja, es heißt eigentlich Gleitkomma.

Coda
2008-11-09, 21:35:49
Ja, stimmt. Wer programmiert schon für die Java Virtual Machine.:biggrin:
Das muss ein Fehler in der Wikipedia sein. Java kann es sich gar nicht leisten etwas anderes zu verwenden, weil sie dann stets Softwareemulation für Gleitkommazahlen einsetzen müssten statt der nativen FPU.

Praktisch ausgeschlossen.

Shink
2008-11-09, 21:40:33
Ich denke es werden kleine Details sein die nicht mit der Spezifikation übereinstimmen.

Das hast du aber gelesen, oder?
http://www.cs.berkeley.edu/~wkahan/JAVAhurt.pdf

Coda
2008-11-09, 22:13:45
Ja. Da geht's vor allem darum, dass Java immer auf float rundet und keinen Zugriff auf die IEEE754-Traps zulässt.

Der Datentyp ist der gleiche.

Monger
2008-11-09, 22:45:06
Über kleine Ganzzahlen iterieren kann man auch mit float als Datentyp. Das hat den Vorteil, dass nicht jedesmal konvertiert werden muss, wenn man in der Schleife mit rechnen möchte.

Findest du wirklich, dass diese Optimierung den Stress wert ist?

Ein Iterator erfordert nunmal eigentlich irgendwas iterierbares, also etwas zählbares. Eine Gleitkommazahl ist aber eben keine natürliche Zahl: eine 1 hinzuzuzählen macht eben die Zahl mitnichten um 1 größer.
Warum etwas als Iterator verwenden, was schlicht ungeeignet ist?

Trap
2008-11-09, 23:01:02
Eine Gleitkommazahl ist aber eben keine natürliche Zahl: eine 1 hinzuzuzählen macht eben die Zahl mitnichten um 1 größer.
float enthält die natürlichen Zahlen zwischen -2^23 und 2^23 und rechnet damit auch richtig.

Solang man Startwert, Endwert und Inkrement im Code als natürliche Zahlen festlegt und nicht von außen bekommt, kann man float und double problemlos zum Iterieren verwenden.

Coda
2008-11-09, 23:02:49
Eine Gleitkommazahl ist aber eben keine natürliche Zahl: eine 1 hinzuzuzählen macht eben die Zahl mitnichten um 1 größer.
Es gibt so viel falsche Mythen um floats dass es schon fast weh tut.

KiBa
2008-11-10, 17:27:46
Es gibt so viel falsche Mythen um floats dass es schon fast weh tut.
das stimmt. allerdings war der satz, den du zitiert hast, vollkommen korrekt. bei großen werten kann eine hinzuaddierte 1 dazu führen, dass sich der wert garnicht ändert.

Coda
2008-11-10, 17:44:50
Es ging aber - wie schon gesagt - um die Range wo das nicht der Fall ist, und die ist mit 2^23 recht groß.

Bei ihm hört sich das so an als würde er denken, dass überhaupt nichts exakt repräsentiert werden würde, oder jede Rechnung automatisch ungenau sei. Das ist einfach nicht der Fall.

ScottManDeath
2008-11-10, 18:23:25
Multiplikation von 2 floats a und b ist bis auf ein epsilon von 2^-24 (fuer 32 bit float) genau.

Addition von 2 floats a und b ist bis auf ein epsilon von ( |a| + |b| ) /(|a + b|) * 2^-24 genau.

Monger
2008-11-10, 19:27:05
Es ging aber - wie schon gesagt - um die Range wo das nicht der Fall ist, und die ist mit 2^23 recht groß.

Nuja, 2^23 sind ein bißchen mehr als 8 Millionen. Passiert natürlich nicht allzu häufig dass eine Schleife über so große Bereiche iteriert, kann aber passieren. Und ein Algorithmus der heimlich, still und leise ab irgendeiner magischen Zahl zusammenbricht, ist nun wirklich kein gutes Beispiel für Robustheit.

Coda
2008-11-10, 19:34:39
Nuja, 2^23 sind ein bißchen mehr als 8 Millionen. Passiert natürlich nicht allzu häufig dass eine Schleife über so große Bereiche iteriert, kann aber passieren. Und ein Algorithmus der heimlich, still und leise ab irgendeiner magischen Zahl zusammenbricht, ist nun wirklich kein gutes Beispiel für Robustheit.
Das der Algorithmus mit Ints bei 2^31 "zusammenbricht" kümmert dich doch sicher auch nicht.

Es ging um hochoptimierte Stellen bei denen man sich dadurch das andauernde Umwandeln von Float->Int sparen kann. Das ist nämlich extrem teuer. Nur weil es in deinen Horizont nicht reingeht, dass sowas durchaus mal vorkommt ist es noch lang nicht "ungeeignet".

#44
2008-11-10, 19:43:05
Beispiel wie man sicherstellen könnte das die floats gleich werden:


float tmpStep = 1.1;
float tmpFaktor = 1;

float tmpStart = 0;
float tmpStop = tmpStep * 10;

while (tmpStart <= tmpStop)
{
std::cout << "start: " << tmpStart << " | stop: " << tmpStop << std::endl;
tmpStart = tmpStep * tmpFaktor++;

if (tmpStart == tmpStop)
{
std::cout << "gleich" << std::endl;
}
}

Das lässt sich auch noch verschönern, dazu bin ich jetzt aber zu faul :)

Trap
2008-11-12, 15:54:53
Ich würde mich hüten zu behaupten, dass Code mit nicht exakt repräsentierbaren floats irgendein definiertes Ergebnis in C++ ergibt.

Es ist an vielen Stellen offengelassen in welcher Reihenfolge und mit welchem Datentyp die Rechnungen tatsächlich ausgeführt werden. Wieviel würdest du darauf wetten, dass dein Code mit allen C++ Compilern auf allen IEEE754-CPUs funktioniert? ;)

Ectoplasma
2008-11-12, 17:31:25
Man kann sich dazu auch dieses Werk zu Gemüte ziehen:

http://gicl.cs.drexel.edu/wiki-data/images/6/6e/Floating_point_math.pdf

pest
2008-11-12, 23:01:03
also wenn man es richtig anstellt kann man auch mit floats exakt rechnen im entsprechenden kontext. mein verlustfreier audiopacker arbeitet intern mit floats/double.

Pinoccio
2008-11-12, 23:08:23
Fließkomma...Fließkomma...was zum Teufel ist FließkommaDie gehobene Variante von Fließpunkt-nummern! (scnr)

mfg

Gast
2008-11-16, 21:08:57
Wenn ihr gerade dabei seid: ein Ausdruck auf 0 zu prüfen, ist das auch problematisch?

z.B. (0.1259-0.1259)*4.458 müsste doch klar 0 sein, oder gibts da auch Abweichungen?

PatkIllA
2008-11-16, 21:43:33
Wenn ihr gerade dabei seid: ein Ausdruck auf 0 zu prüfen, ist das auch problematisch?

z.B. (0.1259-0.1259)*4.458 müsste doch klar 0 sein, oder gibts da auch Abweichungen?
Wo kommen die Zahlen denn her? Wenn du das so im Quelltext schreibst und kompilierst wird das gleich wegoptimiert.
Wenn die Werte durch unterschiedliche Berechnungen zustande kommen, dann wird es in vielen Fällen nicht gleich sein.

Coda
2008-11-17, 02:39:40
Wenn ihr gerade dabei seid: ein Ausdruck auf 0 zu prüfen, ist das auch problematisch?
Ja natürlich.

Gast
2008-11-22, 00:33:27
Okay danke soweit, machen wir es mal etwas konkreter.
Ich habe eine Methode zum berechnen eines eventuellen Schnittpunktes zwischen 2 Geraden, welche durch Vektoren beschrieben werden, zur Berechnung wurde einfach das entstehende LGS nach einer Unbekannten aufgelöst. Jetzt kann man zusätzlich schön prüfen, ob die 2 Geraden nicht doch parallel oder identisch sind, in dem man Zähler und/oder Nenner auf 0 prüft. Deshalb frage ich.. Muss ich hier doch mit nem Schwellenwer arbeiten?





public static double[] Intersect(double a1, double a2, double b1, double b2, double c1, double c2, double d1, double d2)
{
// Zähler
double z = (c2 - a2) * b1 - (c1 - a1) * b2;
// Nenner
double n = d1 * b2 - d2 * b1;

if (z == 0 && n == 0)
{
// sind identisch
}
else if (n == 0)
{
// sind parallel
}
else
{
// Es gibt einen Schnittpunkt, setze z/n in Anfangsgleichung ein...
}

}

#44
2008-11-22, 01:23:27
Wieviel würdest du darauf wetten, dass dein Code mit allen C++ Compilern auf allen IEEE754-CPUs funktioniert? ;)

Was taugt ein Compiler / eine CPU die eine Multiplikation (eines definierten floats mit einem ebenfalls definierten int) nicht reproduzieren kann?

Von daher: Soviel wie die Hersteller des Compilers/der CPU mit der es nicht klappt für einen gefundenen Fehler zahlen.

Trap
2008-11-22, 02:06:26
Was taugt ein Compiler / eine CPU die eine Multiplikation (eines definierten floats mit einem ebenfalls definierten int) nicht reproduzieren kann?
1.1 ist kein float.

#44
2008-11-22, 11:28:34
Das ändert nichts daran das die Multiplikation der Variable mit 10 reproduzierbar ist (u. sein MUSS)...

Das das Ergebniss exakt wäre habe ich nie behauptet. Mir ist klar das es, abhängig von den Werten, nicht so sein muss. Dennoch wird es in beiden Fällen das gleiche (evtl. ungenaue) Ergebniss sein. Siehe den Text den du gequotet hast.