Warum erzeugen diese beiden Codevarianten unterschiedliche Gleitkommaergebnisse?

In diesem Beispiel C ++ - Code-Snippet:

void floatSurprise()
{
    // these come from some sort of calculation
    int a = 18680, b = 3323524, c = 121;
    float m = float(a) / c;

    // variant 1: calculate result from single expression
    float r1 = b - (2.0f * m * a) + (m * m * c);
    cout << "r1 = " << r1 << endl;

    // variant 2: break up the expression into intermediate parts, 
    /// then calculate 
    float
        r2_p1 = 2.0f * m * a,
        r2_p2 = m * m * c,
        r2 = b - r2_p1 + r2_p2;

    cout << "r2 = " << r2 << endl;
}

Die Ausgabe ist:

dev1 = 439703
dev2 = 439702

Wenn im Debugger angezeigt, sind die Werte tatsächlich 439702.50 bzw. 439702.25, was an sich interessant ist - nicht sicher, warum iostream-Drucke standardmäßig ohne den Bruchteil fließen.BEARBEITEN: Der Grund dafür war, dass die Standardpräzisionseinstellung für cout zu niedrig war und cout << setprecision (7) mindestens benötigt, um den Dezimalpunkt für Zahlen dieser Größe zu sehen.

Aber es interessiert mich noch mehr, warum ich unterschiedliche Ergebnisse erhalte. Ich nehme an, es hat mit Rundungen und einem subtilen Zusammenspiel von Ints mit dem erforderlichen Float-Ausgabetyp zu tun, aber ich kann nicht mit dem Finger darauf zeigen. Welcher Wert ist der richtige?

Ich war erstaunt, dass es so einfach war, mit so einem einfachen Code in den Fuß zu schießen. Jeder Einblick wird sehr geschätzt! Der Compiler war VC ++ 2010.

EDIT2: Ich habe einige weitere Untersuchungen mit einer Tabelle durchgeführt, um "korrekte" Werte für die Zwischenvariablen zu generieren, und (über die Ablaufverfolgung) festgestellt, dass sie tatsächlich getrimmt wurden, was zum Präzisionsverlust im Endergebnis beitrug. Ich habe auch ein Problem mit dem einzelnen Ausdruck gefunden, weil ich stattdessen eine praktische Funktion zum Berechnen von Quadraten verwendet habem * m Dort:

template<typename T> inline T sqr(const T &arg) { return arg*arg; }

Obwohl ich freundlich gefragt habe, hat der Compiler dies anscheinend nicht integriert und den Wert separat berechnet. Dabei wurde das Ergebnis abgeschnitten, bevor der Wert an den Ausdruck zurückgegeben und das Ergebnis erneut verzerrt wurde. Autsch.

Antworten auf die Frage(1)

Ihre Antwort auf die Frage