Sehr kleine double-Werte nahe 0 mit < Operator vergleichen gibt falsche Ergebnisse



  • 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 anfangen

    Code 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?


  • Mod

    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?

    😕


  • Mod

    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 immer

    bool 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 habe

    Frage 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 immer

    bool 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



  • Die definitelyLessThan-Relation wird von Knuth in TAOCP, Band 2 in Sektion 4.2.2 definiert, und Transitivität geht dabei in der Tat verloren. Allerdings sind die knuthschen Relationen in der angegebenen Form auf ieee754-floats nur eingeschränkt anwendbar; durch die Einführung der Sonderwerte inf, -inf und insbesondere NaN gelten einige seiner Definitionen nicht mehr. Zum Beispiel ist

    double x = 0.0 / 0.0; // NaN. Wahlweise 0 * inf, inf - inf, inf/inf o.ä.
    
    if(x == x) {
      puts("Passiert nie.");
    } else {
      puts("Hier landen wir.");
    }
    

    mit knuthschen Floats stumpf nicht möglich.

    Das ist etwas, was man bei der Arbeit mit Fließkommazahlen immer im Hinterkopf behalten sollte: Vergleiche mit NaN sind immer falsch, Berechnungen mit NaN sind immer NaN. !(x < y) bedeutet nicht (y >= x). Wenn man beispielsweise in einem Suchverfahren mit der Schleifenbedingung nicht aufpasst, kann man sich damit schon mal Endlosschleifen einhandeln, die bei seltenen Eingabeparametern auftreten und deshalb sogar durch die Tests rutschen können.

    Es ist allerdings auch etwas, was oft nicht im Hinterkopf behalten wird. Denkt daran, wenn ihr das nächste mal jemanden

    double min(double x, double y) { return x < y ? x : y; }
    

    schreiben seht, und weist ihn dann darauf hin, dass min(x, y) so nicht immer das selbe wie min(y, x) ist.



  • Hier wird etwas durcheinandergeschmissen: Das eigentlich Problem sind nicht die Vergleichoperatoren (natürlich kann ich zwei floats auf Gleicheit prüfen!).
    Das Problem ist das die Eigenschaften der gewohnten arithmetischen Operation +,-,*,/ usw. für reelle Zahlen sich nicht auf die floating-point Zahlen Übertragen.


Anmelden zum Antworten