Gleitkommatypen Assoziativgesetz



  • Hallo allersetis,

    ich muss folgendes Programmieren:
    Ich soll beweisen das Gleitkommatypen mit den Operatoren + und * nicht assoziativ sind.

    ich habe mir folgendes ausgedacht:

    #include <iostream>
    #include <cstdlib>
    #include <cmath>
    #include <iomanip>
    using namespace std;
    void Assoziativ(){
    // Allgemein gillt a+(b+c) = (a+b)+c
    // Allgemeint gillt (a*b)*c = a*(b*c)
    int a = 3; int b = 7; int c = 2; int result;
    result = a+(b+c); cout<<"3+(7+2) = "<<result<<endl;
    result = (a+b)+c; cout<<"(3+7)+2 = "<<result<<endl;
    
    }
    void BeweisFloat(){
    float a = 0.05; float b = 0.70; float c =b-a; float result;
    //a+(b+c)
    result = a+(1+b); cout <<"res1 "<<result<<endl;
    result = (a+b)+1; cout <<"res2 "<<result<<endl;
    }
    void BeweisFloatv2(){
    // Gleitkommatypen float, double und long double
    //double a = 10213.32452e10; double b=1; double c = 11.334; double res;
    //(a+b)+c
    double a = 6.*1024*1024*1024*1024*1024; double res;
    res= a;
    res=res+1;
    res=res+1;
    
    cout << setprecision(15) << setw(12) << res << endl;
    //cout<<res<<endl;
    //a+(b+c)
    res=a+2;
    cout << setprecision(15) << setw(12) << res << endl;
    //cout<<res<<endl;
    //result = a+result2; cout<<" "<<result<<" result 2 "<<result2<<endl;
    //cout<<endl;
    //result2 = (a+b);
    //result = result2+c; cout<<" "<<result<<" result 2 "<<result2<<endl;
    //
    
    }
    int main(){
    Assoziativ();
    cout<<endl;
    BeweisFloat();
    system("pause");
    return 0;
    }
    

    ich habe eine Methode Beweis das zunächst zeigen soll was mit Assoziativ gemeint ist und mit den anderen Methoden habe ich versucht zu beweisen das mit dem Typ float das Assoziativgesetz nicht gilt. Ich habe aber das Problem das die beiden ergebnisse gleich sind statt unterschiedlich. Ich weiß nicht warum das so ist was mache ich falsch bzw. wie könnte ich es sonst beweisen ?



  • Du musst dich schon auf eine Unzulänglichkeit der Fließkommazahlen stürzen, um ein Beispiel zu finden. Du könntest z.B. ausnutzen, dass kleine Differenzen zwischen großen Zahlen verschwinden.



  • Danke für die Antwort. Also meinen Sie so etwas in der art 11.0000+(0.0001+0.0011) ? Um ehrlich zu sein habe ich nicht genau verstanden was Sie meinen 🙂



  • ja, sowas in der Art. Spiel mal ein wenig mit den Zahlen rum. Tipp: Es ist einfacher zu zeigen, wenn du eine der Zahlen negativ wählst. Dann kriegste vielleicht am Ende etwa raus wie 0 != 0.0000000001



  • Vielen Dank für die Antwort...
    Also ich habe folgendes probiert:

    void BeweisFloat(){
    float a = 11.0000; float b = -0.0001; float c =0.0011; float result;
    //a+(b+c)
    result = a+(c+b); cout <<"res1 "<<result<<endl;
    result = (a+b)+c; cout <<"res2 "<<result<<endl;
    }
    

    Die Ergebnisse lauten:
    res1 11.001
    res2 11.001

    eigentlich müsste das Ergebnis 11.0010 sein oder nicht ? Also ich habe inzwischen mit vielen Zahlen kombinationen rumgespielt wie z.B. 123.34e10 usw. aber ich bekomme keine unterschiedliche Ergebnisse. Ich wäre über einen weiteren Tipp sehr erfreut 🙂



  • Wo ist der Unterschied zwischen 11.001 und 11.0010?

    Ein weiterer Tipp (der eigentlich schon genannt wurde): Manche Zahlen sollten sehr groß sein, andere sehr klein.

    Edit: Noch ein Tipp: Versuchs mal mit vier statt drei Zahlen. Mit drei gehts zwar auch, finde ich aber etwas schwerer.



  • ich werde es mal probieren...ich habe während dessen folgendes probiert:

    void BeweisFloat(){
    float a = 1.8091; float b = 2.9038; float c =3.8982; float result;
    //a+(b+c)
    //result = a+(c+b); cout <<"res1 "<<result<<endl;
    printf("%.20f\n", result);
    //result = (a+b)+c; cout <<"res2 "<<result<<endl;
    printf("%.20f\n", result);
    }
    

    und ich erhalte folgende Ausgabe:
    8.611100196838378790000
    8.611100196838378790000
    also immer noch keine unterschiedliche Ergebnisse,hmmm



  • (gelöscht, war leider alles an der Aufgabenstellung vorbei)



  • (gelöscht, war leider alles an der Aufgabenstellung vorbei)



  • volkard: Leider alles an der Aufgabenstellung vorbei. Du sollst das Assoziativgesetz widerlegen, nicht das Kommutativgesetz. Lässt sich aber zugegeben schnell umwandeln.

    Für die Addition hab ich mal eine Lösung erstellt (saeba: erst gucken, wenn du aufgibst): http://ideone.com/yWnC9



  • volkard schrieb:

    (Kann sein, daß der Compiler da was wegüberlegt oder die Reihenfolge tauscht, und man doch keinen Unterschied sieht.)

    Ich denke nicht, dass er das darf. Er darf mit höherer Genauigkeit rechnen, aber Assoziativ- und Kommutativgesetze bei Fließkommazahlen anwenden, ist böse für Compiler, vor allem wenn Werte wie NaN, infty und -0 auftreten können.



  • @Michael E.
    ich habe mir dein Lösungsvorschlag angeschaut, da ich leider nicht mehr weiter wusste, aber ich habe jetzt dank dir so eine Idee wie man die Zahlen wählen muss um es für die Multiplikation zu beweisen. Es ist schon merkwürdig warum das bei deiner auswahl der Zahlen es zu unterschiedlichen werte kommt liegt es daran das bei der einen Addition der Rest weggescnitten wird ?



  • saeba schrieb:

    Es ist schon merkwürdig warum das bei deiner auswahl der Zahlen es zu unterschiedlichen werte kommt liegt es daran das bei der einen Addition der Rest weggescnitten wird ?

    Ja, die Mantisse hat nur eine endliche Genauigkeit. Wenn das erste Bit in der Größenordnung 10^10 liegen soll, ist die Mantisse zu kurz, um eine Information in der Größenordnung 10^(-10) zu speichern.



  • Alles klar...

    Vielen Dank für alle die mir geholfen haben das Problem zu lösen, super Forum...sobald ich eine Lösung für die Multiplikation haben werde ich es hier posten...



  • saeba schrieb:

    Alles klar...

    Vielen Dank für alle die mir geholfen haben das Problem zu lösen, super Forum...sobald ich eine Lösung für die Multiplikation haben werde ich es hier posten...

    Das mit der Multiplikation ist gar nicht so selten. Der Trick besteht darin, die Zwischenergebnisse in Variablen zu speichern. Dabei werden sie auf die Genauigkeit von double bzw. float gerundet. Hier ein Beispiel:

    #include <iostream>
    
    template< typename T >
    void mach( int i, int j, int k )
    {
        using namespace std;
        T a = T(1)/i;
        T b = T(1)/j;
        T c = T(1)/k;
        T ab_c = a * b;
        ab_c = ab_c * c;
        T a_bc = b * c;
        a_bc = a * a_bc;
        if( ab_c != a_bc )
        {
            cout << ab_c << " != " << a_bc << endl;
            cout << "auch die Multiplikation von Fliesskommazahlen ist nicht assoziativ" << endl;
        }
    }
    
    int main()
    {
        mach< float >( 3, 7, 9 );
        mach< double >( 3, 5, 9 );
        return 0;
    }
    

    Für die Addition gibt es noch ein schönes Beispiel aus einem Thread im letzten Monat. Es ging um die Berechnung der Eulerschen Zahl e. e wird aus der Summe einer Folge bestimmt, wobei es nicht egal ist, in welcher Reihenfolge die Zahlen addiert werden. Folgendes Codeschnipsel zeigt es:

    #include <iostream>
    #include <cmath> // pow, exp
    #include <vector>
    #include <numeric> // accumulate
    #include <limits> // numeric_limits
    
    // --   berechnet e auf nk Nachkommastellen genau
    double berechne_e( int nk, bool reverse )
    {
        double k = 1.0; // k_0 = 1/(0!) = 1
        std::vector< double > ks( 1, k );
        const double delta = 0.5 * std::pow( 10.0, -nk );
        for( int i=1; ; ++i )
        {
            k /= i;
            ks.push_back( k );;
            if( k <= delta * i ) // entspricht k/i <= delta, wenn i>0
                break;
        }
        return reverse? accumulate( ks.rbegin(), ks.rend(), 0.0 ): // von klein nach groß addieren
            accumulate( ks.begin(), ks.end(), 0.0 ); // von groß nach klein addieren
    }
    
    namespace math
    {
        const double e = std::exp(1.0);
    }
    
    int main()
    {
        using namespace std;
        double e1 = berechne_e( std::numeric_limits< double >::digits10, false );
        double e2 = berechne_e( std::numeric_limits< double >::digits10, true );
        if( e1 != e2 )
        {
            cout << "Die Veraenderung der Reihenfolge fuehrt zu verschiedenen Loesungen" << endl;
            if( math::e == e2 )
                cout << "erst die kleinen Werte addieren, dann wird es genauer" << endl;
        }
        return 0;
    }
    

    Gruß
    Werner


Anmelden zum Antworten