Klammerung mathematischer Ausdrücke verbindlich für Compiler?
-
Werte Forumsbesucher,
bin gerade über eine seltsame Geschichte gestolpert, weil bei zwei Compilern geringe Unterschiede im Output auftreten ...
Wir sind uns einig, daßa * b * c = a * (b * c) = (a * b) * cmathematische Äquvalente sind. Mit begrenzter Rechengenauigkeit sind sie das aber nicht mehr, das weiß jeder, der schon mal mit skalierter Intergerarithmetik herumgepfuscht hat. Angenommen, a und b sind sehr klein, c sehr groß, da ist es unklug, zuerst a und b zu verwursteln und danach mit c zu multiplizieren.
Schlauer wäre es, zuerst (b * c) auszurechnen und dann mit a zu muliplizieren, alsoresult = (a * (b * c))Jetzt hab' ich einen Compiler hier, der die Klammerung anscheinend einfach entfernt und das dem asm- code zufolge soweit möglich (ansonsten Punkt vor Strich usw. alles korrekt) stur left-to-right abarbeitet.
Meine schlichte Frage ist, ob der das theoretisch (also Standard- konform) überhaupt darf
Nicht, daß das jetzt ein Drama für mich darstellt, aber wissen würde ich schon wollen, warum's ab der sechsten Nachkommastelle 'nen Unterschied gibt ...Danke vorab an unsere Standard- Experten

-
Der Compiler darf die Klammern entfernen, weil das Ergebnis mathematisch dasselbe ist. Wenn Du das anders erzwingen willst, wirst Du zwei Schritte machen müssen (und hoffen, daß der Compiler das nicht erkennt wegoptimiert).
-
Belli schrieb:
Der Compiler darf die Klammern entfernen, weil das Ergebnis mathematisch dasselbe ist. Wenn Du das anders erzwingen willst, wirst Du zwei Schritte machen müssen (und hoffen, daß der Compiler das nicht erkennt wegoptimiert).
Das hätte aber eine böse Konsequenz, besonders bei skaliertem Integerzeugs. Mit der "Optimiererei" hatte ich auch schon viel Spaß.
Bislang hatte ich ja auch noch nie echten Schiffbruch erlitten, aber es sammelt sich so was über die Jahre an Geschreibsel an und die Frage wird immer interessanter, wie "sourcekompatibel" sourcekompatibel wirklich ist.
-
pointercrash() schrieb:
Schlauer wäre es, zuerst (b * c) auszurechnen und dann mit a zu muliplizieren, also
result = (a * (b * c))Jetzt hab' ich einen Compiler hier, der die Klammerung anscheinend einfach entfernt und das dem asm- code zufolge soweit möglich (ansonsten Punkt vor Strich usw. alles korrekt) stur left-to-right abarbeitet.
Kannst du mal Codebeispiele posten? Quellcode und Assemblercode? Das sind Ganzzahlmultiplikationen?
Meine schlichte Frage ist, ob der das theoretisch (also Standard- konform) überhaupt darf

IMHO darf er das nicht, wenn es das Ergebnis beeinflussen kann.
-
pointercrash() schrieb:
Meine schlichte Frage ist, ob der das theoretisch (also Standard- konform) überhaupt darf
wenn ich mich nicht irre, darf er die klammern nie unterschlagen. würde mich jedenfalls wundern, wenn's so wäre (ausser bei int-multiplikationen hintereinander, ohne 'ne andere operation dazwischen).

-
+fricky schrieb:
pointercrash() schrieb:
Meine schlichte Frage ist, ob der das theoretisch (also Standard- konform) überhaupt darf
wenn ich mich nicht irre, darf er die klammern nie unterschlagen. würde mich jedenfalls wundern, wenn's so wäre (ausser bei int-multiplikationen hintereinander, ohne 'ne andere operation dazwischen).

denk nochmal ganz scharf über Bashars IMHO nach.
-
volkard schrieb:
denk nochmal ganz scharf über Bashars IMHO nach.
sorry, ich hab bash0rs posting noch nicht gelesen, als ich meins abschickte.

-
pointercrash() schrieb:
Jetzt hab' ich einen Compiler hier, der die Klammerung anscheinend einfach entfernt und das dem asm- code zufolge soweit möglich (ansonsten Punkt vor Strich usw. alles korrekt) stur left-to-right abarbeitet.
Eine Idee ist es erstmal die Optimierung auszuschalten. Wenn das nicht functioniert dann so etwas - Compiler haben i.m.E. sehr grosse Schwierigkeiten Recursionen zu optimieren:
double MyMult (double rIn_0, double rIn_1) { return (rIn_0 * rIn_1); } MyMult (A, MyMult (B, C));Wuerde ich aber gruendlich kommentieren, warum so gemacht - sieht merkwuerdig aus!
-
Rekursion? Wo?
-
Tim schrieb:
Rekursion? Wo?
Weil Du Du MyMult als Parameter in MyMult aufrufst. Du hast eine Recursion der Tiefe 2.
-
Nein, Rekursion ist was anderes.
-
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.