Warum kann der Compiler die Gleitkommaaddition mit 0 nicht optimieren? [duplicate]
Lesezeit: 5 Minuten
Ich habe vier Identitätsfunktionen, die im Wesentlichen nichts tun. Nur Multiplikation mit 1 könnte von clang zu einem einzigen optimiert werden ret Aussage.
float id0(float x) {
return x + 1 - 1;
}
float id1(float x) {
return x + 0;
}
float id2(float x) {
return x * 2 / 2;
}
float id3(float x) {
return x * 1;
}
Und die folgende Compilerausgabe ist: (clang 10, at -O3)
Ich kann verstehen, warum id0 und id2 kann nicht optimiert werden. Sie erhöhen den Wert, der sich dann in positive Unendlichkeit verwandeln könnte und die zweite Operation würde ihn nicht zurückverwandeln.
Aber warum nicht id1 optimiert werden? Eine Addition mit Unendlich würde Unendlich ergeben, eine Addition mit einer beliebigen regulären Zahl würde diese Zahl ergeben und eine Addition mit NaN ergäbe NaN. Warum ist es also keine “echte” Identitätsoperation wie * 1.
Das ist eine wirklich gute Frage. Ich verstehe nicht, warum ich der einzige Upvoter bin.
– Bathseba
1. Juni 2020 um 9:42 Uhr
Gilt nicht für diese spezifischen Beispiele, aber bitte beachten Sie, dass diese Ergebnisse nur von garantiert werden Operatorassoziativität. Das heißt, die additiven Operatoren + und – werden garantiert von links nach rechts ausgewertet. Daher erfolgt die implizite Konvertierung in Float dort, wo sie sollte, möglicherweise durch Glück (?). x + 1 - 1 hat eine andere Bedeutung als 1 - 1 + x . Ersteres ist äquivalent zu (x + (float)1) - (float)1 und letzteres zu (float)((int)1 - (int)1) + x;. Um solche Fehler zu vermeiden, verwenden Sie Float-Konstanten 1.0f.
– Ludin
1. Juni 2020 um 10:01 Uhr
Elfeal
IEEE 754-Gleitkommazahlen haben zwei Nullwerte, einen negativen und einen positiven. Zusammengenommen ergibt sich das positive Ergebnis.
So id1(-0.f) ist 0.fnicht -0.f.
Beachten Sie, dass id1(-0.f) == -0.f Weil 0.f == -0.f.
Beachten Sie auch, dass das Kompilieren mit -ffast-math in GCC führt die Optimierung durch und ändert das Ergebnis.
Verdammt, du warst schneller. Hier ein Gottriegel zur Veranschaulichung.
– n314159
1. Juni 2020 um 9:35 Uhr
Schön geschrieben. Ich fing an, eine Antwort zu schreiben, aber Sie waren zuerst fertig, weil Sie die Situation klarer ausgedrückt haben.
– Jerry Sarg
1. Juni 2020 um 9:35 Uhr
t.niese
“Ich habe vier Identitätsfunktionen, die im Wesentlichen nichts tun.”
Das ist nicht wahr.
Für Fließkommazahlen x + 1 - 1 ist nicht gleich x + 0es ist gleich (x + 1) - 1. Also wenn man zB einen sehr kleinen hat x dann verlieren Sie diesen sehr kleinen Teil in der x + 1 Schritt, und der Compiler kann nicht wissen, ob das Ihre Absicht war oder nicht.
Und im Fall von x * 2 / 2das x * 2 aufgrund der Gleitkommapräzision möglicherweise auch nicht genau, so dass Sie hier einen ähnlichen Fall haben, der Compiler weiß nicht, ob Sie aus irgendeinem Grund den Wert von ändern möchten x auf diese Weise.
Diese wären also gleich:
float id0(float x) {
return x + (1. - 1.);
}
float id1(float x) {
return x + 0;
}
Und diese wären gleich:
float id2(float x) {
return x * (2. / 2.);
}
float id3(float x) {
return x * 1;
}
Das gewünschte Verhalten könnte sicherlich auch anders definiert werden. Aber wie bereits von Nelfeal erwähnt, muss diese Optimierung explizit mit aktiviert werden -ffast-math
Aktivieren Sie den schnellen mathematischen Modus. Diese Option lässt den Compiler aggressive, potenziell verlustbehaftete Annahmen über Gleitkomma-Mathematik treffen. Diese beinhalten:
Fließkomma-Mathematik gehorcht regulären algebraischen Regeln für reelle Zahlen (z. B. + und * sind assoziativ, x/y == x * (1/y) und (a + b) * c == a * c + b * c) ,
Operanden für Gleitkommaoperationen sind nicht gleich NaN und Inf und
+0 und -0 sind austauschbar.
fast-math ist für clang und gcc eine Sammlung von Flags (hier das von clang aufgelistete):
-fno-Ehre-Unendlichkeiten
-fno-honor-nans
-fno-math-fehlernr
-endliche-math
-fassoziativ-math
-reziproke-math
-fno-vorzeichenbehaftete Nullen
-fno-trapping-math
-ffp-Vertrag=schnell
x * 2 ist möglicherweise nicht genau, aber nicht wegen der Gleitkommagenauigkeit, sondern wegen seines Bereichs: x * 2 könnte eine Unendlichkeit erzeugen, wenn x es ist zu groß. Dies ist weniger wahrscheinlich, für x * 2. wie 2. Typ hat double und double hat normalerweise eine größere Reichweite als float.
PS. Fließkommazahlen können Ihnen nach Ihrer Wahl Albträume oder einen Doktortitel bescheren. Sie manchmal Leute töten oder zumindest große finanzielle Katastrophen anrichten (z. B. ein Verlust von mehreren hundert Millionen US$ oder €).
Interessante Links – Besonders den “Menschen töten”-Link muss ich mir merken. Hier ist ein weiterer verwandter: stackoverflow.com/questions/3730019/…
– RobertS unterstützt Monica Cellio
1. Juni 2020 um 10:24 Uhr
Sie töten manchmal Menschen oder verursachen zumindest große finanzielle Katastrophen (z. B. ein Verlust von mehreren hundert Millionen US-Dollar oder €). Das klingt nach einer persönlichen Erfahrung, (ça sent le vécu), haben Sie an der Untersuchung über das Scheitern der teilgenommen Erster Start der Ariane 5?
– chqrlie
1. Juni 2020 um 10:30 Uhr
Wenn x ist ein ruhiger NaN, der zurückkehrt x produziert immer noch id1(x) != x. Das eigentliche Problem ist minus Null wo 1 / id1(x) != 1 / x
– chqrlie
1. Juni 2020 um 10:31 Uhr
@chrqlie: Das Labor, in dem ich in CEA LIST arbeite, hatte Zugriff auf Ariane 5-bezogene Dokumentation, aber ich persönlich hatte keinen Zugriff.
– Basile Starynkevitch
1. Juni 2020 um 10:49 Uhr
12986100cookie-checkWarum kann der Compiler die Gleitkommaaddition mit 0 nicht optimieren? [duplicate]yes
Das ist eine wirklich gute Frage. Ich verstehe nicht, warum ich der einzige Upvoter bin.
– Bathseba
1. Juni 2020 um 9:42 Uhr
Gilt nicht für diese spezifischen Beispiele, aber bitte beachten Sie, dass diese Ergebnisse nur von garantiert werden Operatorassoziativität. Das heißt, die additiven Operatoren + und – werden garantiert von links nach rechts ausgewertet. Daher erfolgt die implizite Konvertierung in Float dort, wo sie sollte, möglicherweise durch Glück (?).
x + 1 - 1
hat eine andere Bedeutung als1 - 1 + x
. Ersteres ist äquivalent zu(x + (float)1) - (float)1
und letzteres zu(float)((int)1 - (int)1) + x;
. Um solche Fehler zu vermeiden, verwenden Sie Float-Konstanten1.0f
.– Ludin
1. Juni 2020 um 10:01 Uhr