Sehr kleine double-Werte nahe 0 mit < Operator vergleichen gibt falsche Ergebnisse
-
Hallo liebe Community,
Ich möchte einen Wert darauf prüfen, ob er kleiner als 0.5 ist. Der Kontext ist eine OpenCascade Anwendung, darum hier nur ein beispielhafter Code:
double abstand=... if(abstand<0.5) { ... } else { ... }Das hat gut funktioniert, bis die Werte ungefähr die Größe 1. e-150 erreichten. Da sollte er ja eigentlich in den if-Zweig springen, tut er aber nicht. Ich weiß, dass man double und floats niemals mit == vergleichen darf, immer nur mit differenz und epsilon. Dass das auch auf die anderen Vergleichsoperatoren zutrifft war mir neu. Ich habe dann folgende Funktion gefunden, die aus einem Buch stammt:
bool definitelyLessThan(float a, float b, float epsilon){ return (b-a) > ((fabs(a)<fabs(b) ? fabs(b) : fabs(a)) * epsilon); }Das hat besser funktioniert aber bei z.B. 6.e-250 hat er dann wieder falsch entschieden.
Was kann ich noch tun? Eine Idee von mir war noch es so zu verkürzen, dass bei so kleinen Werten 0 herauskommt. Aber ich weiß nicht wie ich das umsetzen soll, ohne mir die restliche Funktionalität zu zerstören.Ich freue mich über jeden Denkansatz und jeden Lektürevorschlag

Viele Grüße
Angelnoir
-
angelnoir schrieb:
..., darum hier nur ein beispielhafter Code:
Ich glaube nicht, das dein beispielhafter Code auch das beschriebene Verhalten zeigt.
Bitte http://www.c-plusplus.net/forum/304133 beachten.
-
Zeige doch bitte ein Beispiel zum ausprobieren, mit Compileroptionen und verwendetem Betriebssystem. Ich glaube das auch erst, wenn ich es sehe.
-
Das ist leider nicht so einfach, da wie gesagt die Daten von OpenCacsade kommen. Da ihr aber beide sehr ungläubig seid, muss ich annehmen, dass es eventuell an OpenCascade liegt. Das ist sowieso sehr fehleranfällig.
Ich werde später versuchen mal ein Minimalbeispiel kompilieren zu lassen und zu sehen ob es da auch passiert.
Als Entwicklungsumgebung nutze ich Visual Studio 2010 und mein Betriebssystem ist Windows 7.
An den Compileroptionen kann ich selber allerdings nur wenig umstellen, da das ganze ein Projekt aus meinem HiWi-Job ist. Ich kann das aber an meinen Chef weitergeben (der mir genauso wenig geglaubt hat wie ihr
)Vielen Dank schonmal
-
bei VC++ 2010 wäre interessant, wie die folgenden Compileroptionen gesetzt sind:
Optimization:
alle Optionen, die mit /O anfangenCode Generation:
/arch Use SSE or SSE2 instructions in code generation (x86 only)
/favor Produces code that is optimized for a specific x64 architecture
/fp Specify floating-point behavior
-
Ich habe den Fehler gefunden. Meinen Fehler wohlgemerkt. Ich nahm an, dass mir die Value() Funktion der OpenCascade Klasse einen Wert zurückliefert der sich MyDistValue nennt. Es liefert allerdings MyDistRef, der in diesem Fall immer 5 war...
Entschuldigung für die unnötige Frage

Trotzdem noch die Frage: die Funktion die ich gefunden habe
bool definitelyLessThan(float a, float b, float epsilon){ return (b-a) > ((fabs(a)<fabs(b) ? fabs(b) : fabs(a)) * epsilon); }benutzt man lieber diese oder einfach < ?
-
a<b gibt dir true zurück, wenn a<b gilt, sonst false.
Wenn es das ist, was du testen willst, benutzt du <.Klar, oder?
-
angelnoir schrieb:
Trotzdem noch die Frage: die Funktion die ich gefunden habe
bool definitelyLessThan(float a, float b, float epsilon){ return (b-a) > ((fabs(a)<fabs(b) ? fabs(b) : fabs(a)) * epsilon); }benutzt man lieber diese oder einfach < ?
Da wollte sich wohl jemand über umständliche Fließkommavergleiche lustig machen. Das ist Unsinn.
-
krümelkacker schrieb:
a<b gibt dir true zurück, wenn a<b gilt, sonst false.
Wenn es das ist, was du testen willst, benutzt du <.Klar, oder?
nein, so einfach ist das nicht.
Ihr wisst alle das man wegen der Genauigkeitsfehler nicht einfach == verwendet.Also definiert man z.B.
bool float_eq(a, b) {return abs (a - b) < 0.00001;};
Wenn man also festgelegt hat, dass 0.000001 von der Logik her das gleiche sein soll wie 0, muss ein Vergleich von 0 < 0.000001 auch false zurückgeben!
Damit ergibt sich:
bool float_lt(a, b) {return !float_eq (a,b) && a < b;}
bool float_gt(a, b) {return !float_eq (a,b) && a > b;}<, <=, >=, >= sind also genauso von der Float-Ungenauigkeit betroffen wie == und !=.

-
Real Locator schrieb:
krümelkacker schrieb:
a<b gibt dir true zurück, wenn a<b gilt, sonst false.
Wenn es das ist, was du testen willst, benutzt du <.Klar, oder?
nein, so einfach ist das nicht.
Ihr wisst alle das man wegen der Genauigkeitsfehler nicht einfach == verwendet.Also definiert man z.B.
bool float_eq(a, b) {return abs (a - b) < 0.00001;};
Wenn man also festgelegt hat, dass 0.000001 von der Logik her das gleiche sein soll wie 0, muss ein Vergleich von 0 < 0.000001 auch false zurückgeben!
Damit ergibt sich:
bool float_lt(a, b) {return !float_eq (a,b) && a < b;}
bool float_gt(a, b) {return !float_eq (a,b) && a > b;}<, <=, >=, >= sind also genauso von der Float-Ungenauigkeit betroffen wie == und !=.

Dann ist == nicht mehr transitiv und wir werden alle sterben.
Deswegen definiert man das nicht mal einfach so, sondern setzt es passend zum konkreten Algorithmus sparsam ein.
-
ich hätte auch nicht vorgeschlagen den globalen operator==(bool,bool) oder so zu überschreiben...
oder was ist da nicht transitiv wenn man für float-Vergleiche jetzt nur solche float_...-Funktionen wie im Beispiel nimmt?

-
Real Locator schrieb:
oder was ist da nicht transitiv
bool float_eq(double a, double b) {return abs (a - b) < 0.00001;} double a = 0; double b = 0.00001; double c = (b-a)/2; assert( a == c ); assert( b == c ); assert( a == b ); // ups
-
Real Locator schrieb:
ich hätte auch nicht vorgeschlagen den globalen operator==(bool,bool) oder so zu überschreiben...
Dachte, wie redeten von floats.
Und doch, Du hast beschworen, daß man immerbool float_eq(a, b) {return abs (a - b) < 0.00001;};verwendet. Und mit "Damit ergibt sich:" noch mehr Quatsch eingeläutet.
Real Locator schrieb:
oder was ist da nicht transitiv wenn man für float-Vergleiche jetzt nur solche float_...-Funktionen wie im Beispiel nimmt?
Transitiv: Aus a==b und b==c folgt a==c.
Das ist bei Dir nicht der Fall, wenn a und b ebenso wie b und c dicht genug beieinanderliegen, aber weit genug weg, daß a und c zu weit auseinanderliegen, um als float_eq erkannt zu werden.
Das tötet natürlich manche Algorithmen, die sich auf die Transitivität des == verlassen.
Ebenso wirste mit Deinem float_lt möglicherweise extremen Unspaß beim Sortieren bekommen.
-
Okay also ich stelle fest: bei relativ gleichen Werten, sollte man sich überlegen ob man das mal benutzt
Und in der Uni habe ich gelernt (jetzt kommts :D): niemals floats mit == vergleichen
In meinem Algorithmus habe ich das jetzt nicht benutzt, weil es dort nur auf geringe Genauigkeit ankommt. Hauptsache es ist irgendwie kleiner als 0.5-1
Und ich denke dann dauert die Auswertung von a<b weniger lang als die Funktion.
Diese ist übrigens aus "The Art of Computerprogramming" von Knuth, allerdings weiß ich nicht in welchem Zusammenhang, da ich sie selber nur in einem Forum gefunden habeFrage abseits: Wie schließe ich ein Thema oder lasse schließen? Meine Frage wäre dann nämlich geklärt

Vielen Dank nochmal
-
Du laesst ihn einfach altern.
-
volkard schrieb:
Real Locator schrieb:
ich hätte auch nicht vorgeschlagen den globalen operator==(bool,bool) oder so zu überschreiben...
Dachte, wie redeten von floats.
Und doch, Du hast beschworen, daß man immerbool float_eq(a, b) {return abs (a - b) < 0.00001;};verwendet. Und mit "Damit ergibt sich:" noch mehr Quatsch eingeläutet.
Also ich habe überhaupt nichts "beschworen dass man immer".
Der OP hatte gewusst, dass man bei ==-Vergleichen von floats ggf. aufpassen muss und gefragt ob das auch bei < und > gilt.
Dazu habe ich geschrieben das die Problematik dort natürlich auch zu beachten ist!
Wieso ist das Quatsch???

float_eq ist als Funktion gedacht die man bei Bedarf verwenden kann.
Und genauso benötigt man bei Bedarf evtl. auch noch float_lt, _gt, _le, _ge und _ne.wenn a und b ebenso wie b und c dicht genug beieinanderliegen, aber weit genug weg, daß a und c zu weit auseinanderliegen, um als float_eq erkannt zu werden.
OK, danke.
Damit verstehe ich dann auch was mir Camper sagen wollte (das Beispiel war etwas ungünstig weil es nach der Definition von float_eq ja auch richtig ist, wenn 0 != 0.00001)Meine Frage wäre dann nämlich geklärt

da bin ich mir nicht so sicher

-
Real Locator schrieb:
Also ich habe überhaupt nichts "beschworen dass man immer".
Aber diese Aussage riecht sehr stark danach.
Real Locator schrieb:
Ihr wisst alle das man wegen der Genauigkeitsfehler nicht einfach == verwendet.
Also definiert man z.B.
bool float_eq(a, b) {return abs (a - b) < 0.00001;};Das habe ich so interpretiert, daß Du gesagt hast, daß man nicht einfach == verwenden darf, sondern sowas wie float_eq nehmen muss.
Ich finde auch nicht, daß sie eine andere Auslegung zuläßt.
Aber jetzt wissen wir ja, daß == und float_eq beide ihre Mängel haben und man einfach generell bei float-Vergleichen aufpassen sollte. Bei < und > ist die Sache meistens viel viel unkritischer und man braucht keine Funktionen. Gegebenenfalls läuft die Berechnung halt einen Schritt mehr als dringend notwenig gewesen wäre. Bei Links-Rechts-Entscheidungen dürften die Funktionen sogar regelmäßig falsch sein.
-
angelnoir schrieb:
Diese ist übrigens aus "The Art of Computerprogramming" von Knuth, allerdings weiß ich nicht in welchem Zusammenhang, da ich sie selber nur in einem Forum gefunden habe
zur weiteren Lektüre gucke z.B. hier:
http://www.boost.org/doc/libs/1_34_1/libs/test/doc/components/test_tools/floating_point_comparison.html
http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
-
Real Locator schrieb:
krümelkacker schrieb:
a<b gibt dir true zurück, wenn a<b gilt, sonst false.
Wenn es das ist, was du testen willst, benutzt du <.nein, so einfach ist das nicht.
Doch, ganz so einfach ist das.
Real Locator schrieb:
Ihr wisst alle das man wegen der Genauigkeitsfehler [...bla bla...]
Man sollte schon wissen, dass man sich beim Rechnen mit endlichen Genauigkeiten Rundungsfehler einhaldelt, die sich fortpflanzen. Das widerspricht aber nicht dem, was ich sagte. Die Vergleichsoperatoren funktionieren schon so, wie man das erwarten würde. Ich sagte ja auch: "wenn es das ist, was du testen willst, benutzt du <". Es kann natürlich gut sein, dass ein solcher Test in einem Algorithmus zur Lösung eines bestimmten Problems aufgrund von Rundungsfehlern sinnfrei ist. Da schaut man sich vorher lieber mal um, wie schlaue Numeriker das Problem im konkreten Fall gelöst haben. Viel mehr kann man dazu aber nicht sagen.
Real Locator schrieb:
<, <=, >=, >= sind also genauso von der Float-Ungenauigkeit betroffen wie == und !=.
Inwiefern "betroffen"? Sie tun genau das, was man erwarten würde! Sie vergleichen zwei Werte. Ist der erste kleiner als der zweite wird die < true zurück geben. Wichtig ist nur, dass Leute verstanden haben, dass Rundungsfehler beim Rechnen entstehen und sich fortpflanzen und dass wie Werte a und b die berechnet wurden, nicht unbedingt die sein müssen, die berechnet werden sollten.
-
neben der Rechenungenauigkeit gibt es auch noch weitere Dinge zu beachten, z.B. daß ein double normalerweise mit 64 Bit gespeichert wird, die FP87 Register aber mit 80 Bit rechnen. deshalb haben die verschiedenen Compiler auch Optionen für exakte Arithmetik (d.h. Arithmetik nach IEEE-754). diese "exakte Arithmetik" führt z.B. dazu, daß vor Operationen (auch vor Vergleichen) immer auf 64 Bit gekürzt wird, auch wenn die Werte noch in den Registern sind. ohne diese "exakte Arithmetik" kann es sein, daß zwei Variable von einer vorherigen Rechnung noch in den Registern sind und der Compiler das Verkürzen auf 64 Bit wegläßt, wodurch ein 'ungleich' entsteht. wenn diese Werte dann in anderen Variablen gespeichert und noch einmal verglichen werden, kann ein 'gleich' herauskommen
gucke z.B. hier:
http://www.network-theory.co.uk/docs/gccintro/gccintro_70.html