double Werte vergleichen...funktioniert nicht



  • Hallo alle miteinander,

    Ich habe das Problem, das Vergleichsoperatoren in Verbindung mit double Werten nicht immer, oder oftmals garnicht, funktionieren. Obwohl nach mathematischen Regeln diese Berechnung stimmen sollte wird die if-Anweisung meistens übersprungen:

    ...
    double a =1.5, b =2.25;
    a *= 1.5;
    if (a == b)
    {   cout <<"Wurzel aus " <<b <<" ist " <<a;}
    

    Das ist stark vereinfacht.
    Auf jeden Fall funktionieren folgende Operatoren mit double Werten nicht:
    <= ; >= ; ==

    Gibt es eine Möglichkeit, dieses Problem zu umgehen oder muss ich mir eigene Funktionen für den Vergleich schreiben?

    Danke schonmal für Ratschläge

    Don Quijote



  • liegt an der binären darstellung von flieskommazahlen

    lösungen
    1 nicht mit diesen arbeiten
    2 bereich vergleciehn



  • danke schonmal für die schnelle Antwort f3ee.

    Dann werde ich mir wohl oder übel ein paar funktionen schreiben müssen.



  • /edit: war blödsinn, weil numeric_limits<...>::epsilon sich nur auf die differenz zwischen 1 und der nächstgrößeren darstellbaren zahl bezieht.

    da hatte ich mal wo folgendes gelesen:

    bool nearly_equal(double a, double b)
    {
      return fabs(a - b) <= max(fabs(a), fabs(b)) * numeric_limits<double>::epsilon() * 3;
    
    }
    

    frei nach

    TEQ(X,Y)=DABS(X-Y).LE.DMAX1(DABS(X),DABS(Y))*EPS3
    

    (http://wwwvms.mppmu.mpg.de/vmssig/src/FOR/FUZZY_FORTRAN.FOR)
    jaja, das gute alte fortran 🙂



  • äm...sry, ich bin noch blutiger anfänger,

    aber muss ich da nicht bestimmte dateien einbinden, denn ich bekomme 5 Fehler beim Kompilierversuch.



  • #include <algorithm>
    #include <cmath>
    using namespace std;
    

    btw:

    As a programming practice, one should not compare real numbers for equality, as quantities computed with different but algebraically equivalent formulae will hardly ever be computed equal;

    der link den ich gepostet habe, bespricht das problem ausführlich.



  • ok danke, ich werd mich mal mit dem problem rumschlagen und es hoffentlich lösen.

    Hab schon ne Idee für eine Funktion.
    (hmmm...vlt. schreib ich mir auch eine eigene double-klasse mit entsprechenden operatoren)

    btw queer_boy: der link oben funktioniert bei mir nicht, ich erhalte nur ne seite mir aneinandergereiten sätzen und sonderzeichen.



  • ne ganze klasse nur für nen vergleich von gleitkommazahlen? schreib dir ne kleine funktion, die den betrag der differenz gegen nen epsilon vergleicht und gut is.



  • Don Quijote schrieb:

    ok danke, ich werd mich mal mit dem problem rumschlagen und es hoffentlich lösen.

    Hab schon ne Idee für eine Funktion.
    (hmmm...vlt. schreib ich mir auch eine eigene double-klasse mit entsprechenden operatoren)

    Eine eigene Klasse mit operator==-Methode ist keine gute Idee, weil nur aus der Kenntnis der beiden zu vergleichenden Zahlen, nicht auf ungefähre Gleichheit geschlossen werden kann.

    Schau Dir mal folgendes Programm an:

    #include <iostream>
    
    int main()
    {
        using namespace std;
        double x = 1./7.;
        double y = x + 0.0;
        cout << "Unterschied: " << (x-y) << "  bzw.: " << 100*(x-y)/x << "%" << endl;
    
        y = (x + 1.E+100) - 1.E+100;
        cout << "Unterschied: " << (x-y) << "  bzw.: " << 100*(x-y)/x << "%" << endl;
        return 0;
    }
    

    Im ersten Fall sind die Zahlen nur genau dann gleich, wenn sie wirklich identisch sind. Im zweiten Fall sind sie selbst dann 'gleich', wenn die zweite Zahl zu 0 wird, also zu 100% von der ersten abweicht. Wenn ich also zwei Zahlen x und y habe, kann ich nie sagen ob y das erwartete durch x angegebene Ergebnis enthält. Die zulässige Differenz oder das zulässige Verhältnis der beiden Zahlen lässt sich nur mit der Kenntnis der vorangegangenen Operation angeben.

    erstklassig schrieb:

    ne ganze klasse nur für nen vergleich von gleitkommazahlen?

    .. ansonsten kann man für viele Kleinigkeiten eine 'ganze' Klasse schreiben. Obwohl einige Leute der festen Meinung sind, dass durch Hinschreiben des Wortes 'class' gleich hunderte von Zeilen folgen müssen - das ist im Allgemeinen nicht der Fall. 😉

    Gruß
    Werner



  • @erstklassig:Ich verstehe dich, aber da ich mir zur Zeit noch nicht sicher bin, wie ich das problem angehe, habe ich in meinem letzten Post auch die Abkürzung "vlt." verwendet.
    Ich schließe aus deinem Post, dass dir die Erstellung einer Klasse für ein so "unbedeutendes" Problem zu aufwendig erscheint. 😉
    Mir is das völlig egal, da ich vor ca. 1 Monat mit C++ angefangen hab und mir jede Übung recht ist. Außerdem würde ich dieser Klasse nicht nur die eine Methode hinzufügen, sondern mir gleich einen eigenen "double-datentyp" schreiben, damit ich nicht mehr den Elementaren verwenden muss.

    @Werner Salomon: Danke für deine Ausführung. Genau das ist ein Problem, dass sich mir stellt. Aber ich würde 2 Gleitkommawerte nicht auf diese Weise(n) auf Gleichheit überprüfen, sondern etwas anders:
    der Datentyp double hat eine Genauigkeit von 15 stellen (glaube ich).
    Ich würde die beiden zu vergleichenden Werte erst in Strings umwandeln und diese dann auf Gleichheit überprüfen (mit for-schleife + bool variable).
    Möglicherweise hab ich da jetzt noch einiges Übersehen aber ich probier mal was ich erreichen kann. Ich will mir ja nicht alles von den hilfreichen Mitgliedern dieses Forums auf dem Servierteller präsentieren lassen, sondern auch selbst etwas tüfteln.



  • Don Quijote schrieb:

    Aber ich würde 2 Gleitkommawerte nicht auf diese Weise(n) auf Gleichheit überprüfen, sondern etwas anders:
    der Datentyp double hat eine Genauigkeit von 15 stellen (glaube ich).
    Ich würde die beiden zu vergleichenden Werte erst in Strings umwandeln und diese dann auf Gleichheit überprüfen (mit for-schleife + bool variable).

    Nein bitte nicht ... folgendes fände ich korrekt

    bool nearly_equal( double a, double b, double eps )
    {
      return std::abs(a - b) <= eps;  // erfordert include <cmath>
    
    }
    

    Du gibst von außen an, wie weit die beiden Zahlen voneinander abweichen dürfen. Der Vorschlag von queer_boy ist auch ok, und sollte für viele Anwendungen passen - nur das Verhältnis ist mit 1 : (1 + 3*epsilon) eben konstant. Das sollte Deiner vorausgehenden Berechnung angepasst werden.

    Gruß
    Werner



  • Don Quijote schrieb:

    Ich schließe aus deinem Post, dass dir die Erstellung einer Klasse für ein so "unbedeutendes" Problem zu aufwendig erscheint. 😉

    das hast du falsch verstanden. man kann gern soviele klassen verwenden, wie man lustig ist. es spricht absolut nix dagegen, klassen zu deklarieren, die aus nix weiter als einem member bestehen und an zwei stellen verwendet werden, wenns irgendeinem sinnvollen zweck dient, und sei dieser zweck nur bessere übersichtlichkeit.

    aber hier geht es um die einführung eines ersatzes für einen primitiven typen, der sich in allen belangen exakt gleich einem double verhält bis auf den vergleichsoperator. das sorgt für mehr verwirrung, als es nutzen bringt. um das ganze konsequent durchzuführen, solltest du an allen stellen deinen typen anstatt von doubles verwenden.
    manchmal ist es nicht verkehrt, mit eigenen typen zu rechnen. aber in diesem fall stehen kosten und nutzen in keinem verhältnis.

    eine simple funktion, die diesen vergleich für zwei doubles durchführt hat wesentlich weniger seiteneffekte und sorgt für übersichtlichkeit und konsistenz im code.



  • erstklassig schrieb:

    ...aber hier geht es um die einführung eines ersatzes für einen primitiven typen, der sich in allen belangen exakt gleich einem double verhält bis auf den vergleichsoperator. das sorgt für mehr verwirrung, als es nutzen bringt. um das ganze konsequent durchzuführen, solltest du an allen stellen deinen typen anstatt von doubles verwenden....

    @erstklassig: da hast du natürlich vollkommen Recht, eine neue Klasse bloß wegen einer Operatorfunktion ist unnötig. Wenn ich diese Klasse schreibe, erstelle ich Sie natürlich, um sie auch permanent anstelle von "doubles" zu verwenden. Dabei würde ich auch alle anderen Vergleichsoperatoren, sofern möglich, überladen. (+einige extras)
    Da haben wir wohl etwas aneinander vorbei geredet. 😉

    Werner Salomon schrieb:

    Nein bitte nicht...

    @Werner Salomon: Sry, aber wieso nicht?
    Also ich hab mich mal hingesetzt und mir überlegt, wie es zu realisieren ist.
    Dabei bestand nur das Problem, das beispielsweise das Ergebnis von 1.5 * 1.5 laut PC folgendes ist: 2.250000000000001. (Bei allen anderen Quadratzahlen verhält es sich ähnlich)

    Da die Ergebnisse meiner Berechnungen alle max. 14 Nachkommastellen haben, streiche ich die letzte Ziffer der Zahl einfach weg (natürlich nur, wenn die Zahl tatsächlich 17 Zeichen(der punkt inklusive) hat).
    Ich habe nun folgende Funktion, die genau das macht, was ich möchte (bisher jedenfalls).

    Hier die Bedeutung einiger Funktionen:
    -length(string) ->berechnet die Anzahl der Stellen eines Strings
    -stod(string) -> wandelt einen string in einen double-Wert um
    -dotos(double) -> wandelt einen double-Wert in einen String um

    Hier die Funktion:

    bool dvgl(double a, double b)
    {
    	int lstr;
       string a1 ="", a2 ="";
    
    	if(length(dotos(a)) == 17)
    	{
    		string tmp1 =dotos(a);
    		double d1;
    		for(int i = 0; i < 16; i++)
    		{   a1 += tmp1[i];}
    		d1 =stod(a1);
    		a1 = dotos(d1);
    	}
       else
       {   a1 = dotos(a);}
    
       if(length(dotos(b)) == 17)
    	{
    		string tmp2 =dotos(b);
    		double d2;
    		for(int i = 0; i < 16; i++)
    		{   a2 += tmp2[i];}
    		d2 = stod(a2);
    		a2 = dotos(d2);
    	}
       else
       {   a2 = dotos(b);}
    
    	length(a1) - length(a2) < 0 ? lstr = length(a2): lstr = length(a1); 
    
    	for(int i = 0;i < lstr;i++)
    	{
    		if(a1[i] != a2[i])
          {   return false;}
    	}
    
    	return true;
    }
    


  • [ironie]Last uns alle Performance über bort werfen! Wir haben doch so schnelle neue PCs, warum da nicht direkt eine Schliefe in einem Thread packen die garnix macht und dann vllt. auch noch mal irgendwo nen paar Dateien einlesen und wieder ausgeben? Och kommt ... Performance spielt keine Rolle mehr![/ironie]



  • Verar***en kann ich mich selbst, danke.

    1. Ich bin noch Anfänger
    2. Ich wusste nicht, wie ich es anders machen sollte
    3. Mir ist bewusst, dass diese Funktion nicht gerade Ressourcen-schonend ist
    4. Wenn du Verbesserungsvorschläge hast, dann teil sie mir doch bitte mit anstatt nur rumzumaulen.

    😉

    Ich hätte nur folgende Verbesserung:

    bool dvgl(double a, double b) 
    { 
        int lstr; 
        string a1 =dotos(a), a2 =dotos(b); 
    
        if(length(a1) == 17) 
        { 
            double d1; 
            a1[length(a1)] = '\0'; 
            d1 = stod(a1); 
            a1 = dotos(d1);
        } 
    
       if(length(a2) == 17) 
        {  
            double d2; 
            a2[length(a2)] = '\0'; 
            d2 = stod(a2); 
            a2 = dotos(d2); 
        }  
    
        length(a1) - length(a2) < 0 ? lstr = length(a2): lstr = length(a1); 
    
        for(int i = 0;i < lstr;i++) 
        { 
            if(a1[i] != a2[i]) 
          {   return false;} 
        } 
    
        return true; 
    }
    


  • Don Quijote schrieb:

    Verar***en kann ich mich selbst, danke.
    2) Ich wusste nicht, wie ich es anders machen sollte
    3) Mir ist bewusst, dass diese Funktion nicht gerade Ressourcen-schonend ist
    4) Wenn du Verbesserungsvorschläge hast, dann teil sie mir doch bitte mit anstatt nur rumzumaulen.

    Verwende bei Gleitkommawerten NIEMALS Stringvergleich sondern immer Vergleich gegen ein gegebenes Delta. Es hat seine guten Gründe warum man dies so tut und nicht über Strings geht (Ganz davon abgesehen das es auch weniger Zeilen sind).

    cu André


Anmelden zum Antworten