Pourquoi ces deux variantes de code produisent-elles des résultats en virgule flottante différents?

Étant donné cet exemple d’extrait de code C ++:

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; } 

La sortie est:

dev1 = 439703
dev2 = 439702

Lorsqu’elles sont visualisées dans le débogueur, les valeurs sont respectivement 439702.50 et 439702.25, ce qui est intéressant en soi – vous ne savez pas pourquoi iostream imprime des flottants sans la partie fractionnaire par défaut. EDIT: La raison en était que le paramètre de précision par défaut pour cout était trop bas, cout << setprecision (7) au moins pour voir le point décimal des nombres de cette grandeur.

Mais je suis encore plus intéressé à savoir pourquoi j’obtiens des résultats différents. Je suppose que cela a à voir avec l’arrondi et une certaine interaction subtile des ints avec le type de sortie float requirejs, mais je ne peux pas mettre le doigt dessus. Quelle est la valeur correcte?

J’ai été étonné qu’il soit si facile de me tirer une balle dans le pied avec un code aussi simple. Toute idée sera grandement appréciée! Le compilateur était VC ++ 2010.

EDIT2: J’ai effectué des recherches plus approfondies en utilisant un tableur pour générer des valeurs “correctes” pour les variables intermédiaires et ai constaté (via le traçage) que celles-ci étaient effectivement réduites, consortingbuant ainsi à la perte de précision dans le résultat final. J’ai aussi trouvé un problème avec l’expression simple, car j’ai utilisé une fonction pratique pour calculer des carrés au lieu de m * m :

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

Même si j’ai demandé gentiment, le compilateur ne l’a apparemment pas inséré, et a calculé la valeur séparément, en ajustant le résultat avant de renvoyer la valeur à l’expression et en modifiant à nouveau le résultat. Aie.

Vous devriez lire ma longue et longue réponse sur les raisons pour lesquelles la même chose se produit en C #:

(.1f + .2f ==. 3f)! = (.1f + .2f) .Equals (.3f) Pourquoi?

En résumé: tout d’abord, vous n’obtenez que sept décimales de précision avec float. La bonne réponse que vous y avez faite avec une arithmétique exacte pendant tout le calcul est d’environ 439702.51239669 … donc vous vous approchez de la bonne réponse compte tenu des limites d’un flottant, dans les deux cas.

Mais cela n’explique pas pourquoi vous obtenez des résultats différents avec ce qui ressemble exactement aux mêmes calculs. La réponse est la suivante: le compilateur est autorisé à utiliser une large lattitude pour rendre vos calculs plus précis et, apparemment, vous avez rencontré deux cas où l’optimiseur prend ce qui est logiquement la même expression et ne les optimise pas jusqu’au même code.

Quoi qu’il en soit, lisez attentivement ma réponse concernant C #; tout ce qu’il y a dedans s’applique aussi bien au C ++.