Klammerung mathematischer Ausdrücke verbindlich für Compiler?



  • SG1 schrieb:

    Nein, Rekursion ist was anderes.

    OK - strikly spoken, aber wenn Du ein "richtige" haben moechtest:

    typedef struct MultiData
    {
        double       rData;
        MultiData   *pNext = NULL;
    }
    
    double MyMulti (MultiData *pIn,
                    double     rValue)
    {
        if (pIn == NULL)
            return rValue;
        else
            return MyMulti(pIn->pNext, pIn->rValue * rValue);
    }
    
    int main ()
    
    {
        MultiData  aData [3];
    
        /* Irgentebbes */
    
        aData [0]->rValue = A;
        aData [1]->rValue = B;
        aData [2]->rValue = C;
    
        aData [0]->pNext = aData [1];
        aData [1]->pNext = aData [2];
    
        D = MyMulti (aData, 1);
    
        /* Noch ebbes */
    }
    

    Zufrieden?



  • hartmut1164 schrieb:

    SG1 schrieb:

    Nein, Rekursion ist was anderes.

    OK - strikly spoken, aber wenn Du ein "richtige" haben moechtest:

    typedef struct MultiData
    {
        double       rData;
        MultiData   *pNext = NULL;
    }
    
    double MyMulti (MultiData *pIn,
                    double     rValue)
    {
        if (pIn == NULL)
            return rValue;
        else
            return MyMulti(pIn->pNext, pIn->rValue * rValue);
    }
    
    int main ()
    
    {
        MultiData  aData [3];
    
        /* Irgentebbes */
    
        aData [0]->rValue = A;
        aData [1]->rValue = B;
        aData [2]->rValue = C;
    
        aData [0]->pNext = aData [1];
        aData [1]->pNext = aData [2];
    
        D = MyMulti (aData, 1);
    
        /* Noch ebbes */
    }
    

    Zufrieden?

    Hey, das kann aber gefährlich werden, denn aData ist nicht mit Null initialisiert, daher wird aData[2].pNext auch nicht Null sein, außerdem sind deine Zuweisungsoperationen illegal, da dein Array keine Zeiger hält.



  • feraL schrieb:

    Hey, das kann aber gefährlich werden, denn aData ist nicht mit Null initialisiert, daher wird aData[2].pNext auch nicht Null sein, außerdem sind deine Zuweisungsoperationen illegal, da dein Array keine Zeiger hält.

    War etwas schnell mit den Finger - Pointer-Stew gebaut:

    #include <stdio.h>
    
    struct MultiData
    {
        double              rValue;
        struct MultiData   *pNext;
    };
    
    double MyMulti (struct MultiData  *pIn,
                    double             rValue)
    {
        if (pIn == NULL)
            return rValue;
        else
            return MyMulti(pIn->pNext, pIn->rValue * rValue);
    }
    
    int main ()
    
    {
        struct MultiData  aData [3];
    
        aData[0].rValue = 3.0;
        aData[1].rValue = 4.0;
        aData[2].rValue = 5.0;
    
        aData [0].pNext = &aData [1];
        aData [1].pNext = &aData [2];
        aData [2].pNext = NULL;
    
        printf ("%e", MyMulti (aData, 1));
    
        return 0;
    }
    


  • Also da finde ich sowas wie:

    temp = b * c;
    result = a * temp;

    ein klein wenig übersichtlicher, um eine bestimmte Ausführungsreihenfolge zu erzwingen ...



  • Belli schrieb:

    Also da finde ich sowas wie:

    temp = b * c;
    result = a * temp;

    ein klein wenig übersichtlicher, um eine bestimmte Ausführungsreihenfolge zu erzwingen ...

    Hm, in deinem ersten Post in diesem Thread hast du noch geschrieben, bei solchen Konstrukten müsste man hoffen, dass der Compiler das nicht wegoptimiert.



  • Bashar schrieb:

    Belli schrieb:

    Also da finde ich sowas wie:

    temp = b * c;
    result = a * temp;

    ein klein wenig übersichtlicher, um eine bestimmte Ausführungsreihenfolge zu erzwingen ...

    Hm, in deinem ersten Post in diesem Thread hast du noch geschrieben, bei solchen Konstrukten müsste man hoffen, dass der Compiler das nicht wegoptimiert.

    Jo, stimmt, ich habe keine Ahnung, wie intelligent so ein Compiler ist. Trotzdem würde ich das zunächst so probieren, bevor ich derart wilde Rekursionen entwickeln würde.



  • Warum denn nicht einfach a * (b * c) ? Wenn die Reihenfolge _nicht_ egal ist, und der Compiler es trotzdem umdreht, ist der Compiler kaputt.

    Wenn a, b, und c z.B. unsigned ints sind, darf der Compiler sie natürlich in der Reihenfolge zusammenmultiplizieren, die ihm am meisten Spaß macht. Und braucht sich auch nicht durch überflüssige, wegoptimierbare Zusatzvariablen und Rekursionen daran hindern lassen.



  • namespace invader schrieb:

    Warum denn nicht einfach a * (b * c) ? Wenn die Reihenfolge _nicht_ egal ist, und der Compiler es trotzdem umdreht, ist der Compiler kaputt.

    Wenn a, b, und c z.B. unsigned ints sind, darf der Compiler sie natürlich in der Reihenfolge zusammenmultiplizieren, die ihm am meisten Spaß macht.

    Meine These ist, daß er das unabhängig vom Datentyp darf, einfach weil die Multiplikation kommutativ ist.
    Ich habe ehrlich gesagt das Problem 'skalierte Integerarithmetik' nicht verstanden, bin aber unabhängig davon der Meinung, daß der Compiler die Klammern dann nicht zu beachten braucht, wenn das Ergebnis (rein mathematisch betrachtet) davon nicht beeinträchtigt wird.



  • Belli schrieb:

    Meine These ist, daß er das unabhängig vom Datentyp darf, einfach weil die Multiplikation kommutativ ist.
    Ich habe ehrlich gesagt das Problem 'skalierte Integerarithmetik' nicht verstanden, bin aber unabhängig davon der Meinung, daß der Compiler die Klammern dann nicht zu beachten braucht, wenn das Ergebnis (rein mathematisch betrachtet) davon nicht beeinträchtigt wird.

    Jaja, Thesen, Vermutungen, ...

    1.) Hat sich jemand mal den Standard angesehen oder Assembler Code angeschaut?
    2.) Welche Rolle spielt es fuer den Compiler, nicht die Reihenfolge des Nutzer zu beachten? (a*b)*c benoetigt 3 Multiplikationen genau wie a*(b*c).
    3.) Rein mathematisch ist schoen und gut, aber wir rechnen im Computer immer mit diskreten Werten. Problematisch wird es wenn sehr grosse reelle Zahlen mit sehr kleinen reellen Zahlen multipliziert/addiert werden. Dort kann das Ergebnis stark von der Reihenfolge der Operationen abhaengen.



  • Der Standard sagt dazu nicht viel explizit, man kann aber das Verhalten ableiten. Ein Ausdruck wie a*(b*c) besteht aus zwei Multiplikationsausdrücken. Der eine ist b*c, der andere besteht aus a und dem Ergebnis von b*c. Folglich würde die abstrakte Maschine zuerst b*c berechnen, dann a damit multiplizieren.
    Falls a,b,c komplexe Ausdrücke sind, ist die Auswertungsreihenfolge nicht festgelegt, bei a()*(b()*c()) dürfte also durchaus erst a() aufgerufen werden, dann b()*c() ausgerechnet und zum Schluss multipliziert werden.
    Der Compiler darf jetzt jeglichen Code generieren, dessen Semantik mit der der abstrakten Maschine übereinstimmt. Das ist der Freibrief für den Optimierer. (BTW, die Semantik der abstrakten Maschine ist nicht über mathematische Idealisierungen definiert, sondern über Bits und Bytes) Ausnahmen von dieser Regel müssen explizit definiert sein. Sowas gibt es für floating-point-Ausdrücke, bei denen der Compiler gewisse Ausdrücke zusammenfassen darf, wobei eventuell sich ein anderes Rundungsverhalten ergibt. Für normale Ganzzahlausdrücke hab ich sowas nicht gefunden.



  • knivil schrieb:

    Belli schrieb:

    Meine These ist, daß er das unabhängig vom Datentyp darf, einfach weil die Multiplikation kommutativ ist.
    Ich habe ehrlich gesagt das Problem 'skalierte Integerarithmetik' nicht verstanden, bin aber unabhängig davon der Meinung, daß der Compiler die Klammern dann nicht zu beachten braucht, wenn das Ergebnis (rein mathematisch betrachtet) davon nicht beeinträchtigt wird.

    Jaja, Thesen, Vermutungen, ...

    Jaja ... schön, daß Du hier endlich mal mit handfesten Aussagen Licht ins Dunkel bringst ...

    knivil schrieb:

    1.) Hat sich jemand mal den Standard angesehen oder Assembler Code angeschaut?

    Im Eingangsposting steht zu lesen, daß sich der Threadersteller den ASM-Code angesehen hat.

    knivil schrieb:

    2.) Welche Rolle spielt es fuer den Compiler, nicht die Reihenfolge des Nutzer zu beachten? (a*b)*c benoetigt 3 Multiplikationen genau wie a*(b*c).

    Ähhh ..., ist das jetzt neu, oder waren wir nicht schon lange so weit?

    knivil schrieb:

    3.) Rein mathematisch ist schoen und gut, aber wir rechnen im Computer immer mit diskreten Werten. Problematisch wird es wenn sehr grosse reelle Zahlen mit sehr kleinen reellen Zahlen multipliziert/addiert werden. Dort kann das Ergebnis stark von der Reihenfolge der Operationen abhaengen.

    Hier könnte das Problem des Threaderstellers liegen ... das hat er aber selbst schon erkannt, aber super, daß Du das noch mal erwähnst.



  • Bashar schrieb:

    bei a()*(b()*c()) dürfte also durchaus erst a() aufgerufen werden, dann b()*c() ausgerechnet und zum Schluss multipliziert werden.

    Das Problem (und die Frage nach der Zulässigkeit) hier ist laut TE aber, ob auch erst a() * b() ausgerechnet werden darf, um dann erst mit c() zu multiplizieren.



  • Ich habe keine Ahnung, ob es in diesem Zusammenhang von Belang ist:
    Ich habe gerade mal in C++ eine Testklasse mit überladenem + - Operator erstellt und getestet, dort werden - auch mit Optimierungsschalter beim Compile-Vorgang - die Klammern beachtet. Es ist dort leicht zu testen, da man in der Überladenen Funktion einen Text ausgeben kann.



  • Belli schrieb:

    Das Problem (und die Frage nach der Zulässigkeit) hier ist laut TE aber, ob auch erst a() * b() ausgerechnet werden darf, um dann erst mit c() zu multiplizieren.

    Darauf bin ich eingegangen. Halte mir doch nicht vor, dass ich noch etwas umfassender antworte.

    Ich habe gerade mal in C++ eine Testklasse mit überladenem + - Operator erstellt und getestet, dort werden - auch mit Optimierungsschalter beim Compile-Vorgang - die Klammern beachtet. Es ist dort leicht zu testen, da man in der Überladenen Funktion einen Text ausgeben kann.

    Das ist nicht relevant, da der +-Operator als Funktionsaufruf erkannt wird, so dass die späteren Compilerstufen gar keine Addition mehr zu Gesicht bekommen.



  • Bashar schrieb:

    Belli schrieb:

    Das Problem (und die Frage nach der Zulässigkeit) hier ist laut TE aber, ob auch erst a() * b() ausgerechnet werden darf, um dann erst mit c() zu multiplizieren.

    Darauf bin ich eingegangen.

    Das habe ich nicht erkannt.

    Bashar schrieb:

    Ich habe gerade mal in C++ eine Testklasse mit überladenem + - Operator erstellt und getestet, dort werden - auch mit Optimierungsschalter beim Compile-Vorgang - die Klammern beachtet. Es ist dort leicht zu testen, da man in der Überladenen Funktion einen Text ausgeben kann.

    Das ist nicht relevant, da der +-Operator als Funktionsaufruf erkannt wird, so dass die späteren Compilerstufen gar keine Addition mehr zu Gesicht bekommen.

    Ja, das macht Sinn. Ist wohl nicht vergleichbar mit dem Problem des TE.



  • Bashar schrieb:

    ... Der Compiler darf jetzt jeglichen Code generieren, dessen Semantik mit der der abstrakten Maschine übereinstimmt. Das ist der Freibrief für den Optimierer. (BTW, die Semantik der abstrakten Maschine ist nicht über mathematische Idealisierungen definiert, sondern über Bits und Bytes) Ausnahmen von dieser Regel müssen explizit definiert sein. Sowas gibt es für floating-point-Ausdrücke, bei denen der Compiler gewisse Ausdrücke zusammenfassen darf, wobei eventuell sich ein anderes Rundungsverhalten ergibt. Für normale Ganzzahlausdrücke hab ich sowas nicht gefunden.

    Du dürftest richtig liegen. Konnte wegen Probs an anderen Fronten das noch nicht 100%ig dingfest machen, aber die Geschichte betrifft zumindest einmal doubles, Integerzeugs schichtet er nicht um, das wär' nämlich wirklich schlimm.
    Schon irre, da ist was so wohldefiniert und trotzdem touchiert man immer wieder Grenzbereiche ...



  • Du hattest was von skalierten Integern, also Fixpunktarithmetik, erwähnt, deshalb hätte ich jetzt nicht gedacht, dass das relevant für dein Problem ist.

    Der Absatz 6.5§8 lautet jedenfalls:
    "A floating expression may be contracted, that is, evaluated as though it were an atomic
    operation, thereby omitting rounding errors implied by the source code and the
    expression evaluation method.64) The FP_CONTRACT pragma in <math.h> provides a
    way to disallow contracted expressions. Otherwise, whether and how expressions are
    contracted is implementation-defined."



  • pointercrash() schrieb:

    Integerzeugs schichtet er nicht um, das wär' nämlich wirklich schlimm.

    Da ist es aber egal. Bei ints spielt die Reihenfolge nämlich keine Rolle, das Ergebnis ist selbst bei Overflows das selbe.

    Ich glaube schon, dass er es bei a*(b*c) darf, da da kein Sequencepoint dazwischen ist. ; ist unter anderem ein Sequencepoint und deswegen dürfte t = b*c; a*t nicht umgeklammert werden. Ich bin mir da aber nicht sicher.



  • Ben04 schrieb:

    Ich glaube schon, dass er es bei a*(b*c) darf, da da kein Sequencepoint dazwischen ist. ; ist unter anderem ein Sequencepoint und deswegen dürfte t = b*c; a*t nicht umgeklammert werden. Ich bin mir da aber nicht sicher.

    Würde diese Argumentation nicht auch bedeuten, dass ein Compiler z.B. nicht Funktionen inlinen darf? 😉



  • Nochmal, es gibt überhaupt keine Regel, wie der Code genau auszusehen hat. Der Compiler darf überflüssige Rechnungen machen, der darf exakt das machen was dasteht, der darf auch alles bis zur Unkenntlichkeit verquirlen. Hauptsache, es kommt, sofern wir uns nicht im undefinierten bewegen, das gleiche beobachtbare Verhalten raus, das die abstrakte Maschine liefern würde. (Ausnahmen wie dieses 'contracting' bestätigen die Regel.)
    Sequenzpunkte haben ja mit Seiteneffekten zu tun. Wenn die Zuweisungen aber jetzt nicht gerade an volatile ist, und der Compiler seinen Datenfluss so organisiert hat, dass das Verhalten passt, dann kann er auch die zeitliche Reihenfolge verdrehen, wenn es ihm passt. Der Unterschied wäre ja nicht legal wahrnehmbar.


Anmelden zum Antworten