1
votes

est (0,1 + 0,2) == 0,3 vrai ou faux?


7 commentaires

Quel compilateur utilisez-vous? J'obtiens false pour votre premier bloc de code ci-dessus avec gcc. (Probablement parce que 0.3 est un littéral double .)


printf ("% d \ n", (int) (sizeof (double) - sizeof (float)));


Je pense que c'est utile: stackoverflow.com/questions/588004/... .


Le mode d'arrondi est implémenté défini et peut être modifiable.


Juste pour clarifier (et en plus du commentaire de DevSolar), le mode d'arrondi est généralement hors de propos ici, et un hareng rouge; tout autre mode d'arrondi afficherait le même comportement dans ce cas, car la précision de float est trop petite pour exprimer la différence entre 0.1f + 0.2f et 0.3 f (avant même d'arrondir!). Quoi qu'il en soit, il n'y a pas une «petite» chance que l'arrondi échoue: il y a une infinité nombres où l'arithmétique float vous donnera des résultats erronés (comme il y en a cas où l'arithmétique à virgule flottante décimale à précision limitée donnera des résultats erronés).


L'espérance naïve d'environ 0.x + 0.y est vraie à 84% en double précision et 88% en simple précision avec le mode d'arrondi par défaut. Il est d'environ 78% pour une fraction composée de 2 à 4 chiffres (décimaux), à la fois en simple et en double précision.


@KonradRudolph ce que vous écrivez est correct, mais je n'aime pas le terme est trop petit car il pourrait laisser penser que les attentes fonctionnent en raison d'une petite précision. Avec 1 bit de précision en moins - 23 bits significand - 0,1 + 0,2 ne serait pas égal à 0,3. 0,2 est évidemment sur le prochain binade que 0,1, et leur somme est sur le prochain binade. Donc, le moindre bit de la somme exacte est 1/4 ulp. Selon le mode d'arrondi, cela peut faire une erreur jusqu'à 3/4 ulp (1/2 ulp ici au plus car 0,2 est 2 * 0,1). La relation avec la précision en virgule flottante concerne les bits de fin de 0,1 dans la représentation en virgule flottante, et elle est périodique.


3 Réponses :


7
votes

J'obtiens false pour votre programme, pas true comme vous l'indiquez dans votre question. ( 0.3 est un littéral double , donc je suppose que lorsque vous avez testé cela localement, vous avez utilisé une variable float au lieu d'un 0.3 littéral.) Si vous utilisez réellement float ( == 0.3f au lieu de == 0.3 ), vous obtenez true comme sortie car il se trouve qu'avec float , 0,1 + 0,2 == 0,3 est vrai.

Mais strong>, le point fondamental reste que le IEEE-754 à virgule flottante binaire utilisé par float (simple précision) et double (double précision) est très rapide à calculer et utile pour beaucoup de choses, mais par nature imprécis pour certaines valeurs, et donc vous obtenez ce genre du problème. Avec float , vous obtenez true pour 0,1 + 0,2 == 0,3 , mais vous obtenez false pour 0,1 + 0,6 == 0,7 :

#include <stdio.h>

int main() {
    printf("%s\n", 0.1 + 0.2 == 0.3 ? "true" : "false"); // prints false
    //             ^^^−−−^^^−−−−^^^−−− `double` literals
    return 0;
}

Le célèbre 0.1 + 0.2 == 0.3 version de ce problème arrive à double:

#include <stdio.h>

int main() {
    printf("%s\n", 0.1f + 0.6f == 0.7f ? "true" : "false"); // prints false
    //             ^^^^−−−^^^^−−−−^^^^−−− `float` literals
    return 0;
}


2 commentaires

Crowder Merci pour votre excellente réponse, oui j'utilisais 0,3 comme variable flottante localement, maintenant je vois. Btw pourquoi le nombre à virgule flottante est-il vraiment rapide à calculer?


@slowjams - Parce qu'ils sont conçus de cette façon. L ' article Wikipédia peut contenir plus de détails.



1
votes

Généralement, toutes les rvaleurs float sont déclarées comme double (par exemple 0.5, 11.332, 8.9, etc.) Ainsi, lorsque vous écrivez l'instruction suivante:

0.10000000000000000555 // 0.1
0.2000000000000000111  // 0.2
0.2999999999999999889  // 0.3 -------- NOTE
0.30000000000000004441 // 0.1 + 0.2 -- NOTE

Il évalue 0,1 + 0,2 = 0,3, c'est correct, mais dans le côté droit, le 0.3 ne fonctionne pas comme vous vous attendez à ce qu'il soit, comme déjà mentionné, il est déclaré comme double par défaut.

Ainsi, le compilateur essaie de comparer la valeur:

#include <iomanip>
.
.
.
cout << setprecision(20) << 0.1 << endl;
cout << setprecision(20) << 0.2 << endl;
cout << setprecision(20) << 0.3 << endl;
cout << setprecision(20) << (0.1 + 0.2) << endl;

Ce qui n'est clairement pas égal. p >

Pour résoudre ce problème, vous devez ajouter un suffixe pour exprimer le compilateur que vous essayez d'utiliser une valeur à virgule flottante.

Essayez plutôt ceci:

(a + b == 0.3F)


0 commentaires

1
votes

Au sens mathématique, 0,1 + 0,2 == 0,3 est toujours vrai

Au sens de virgule flottante, 0,1 + 0,2 == 0,3 n'est vrai que si erreur de représentation de 0,1 + erreur de représentation de 0,2 == erreur de représentation de 0,3

Comme je suis sûr que vous le savez, les erreurs dépendent de la valeur et de son ampleur. En tant que tel, il existe quelques cas où les erreurs s'alignent de telle sorte que les nombres à virgule flottante semblent fonctionner pour l'égalité, mais le cas général est que de telles comparaisons en général sont erronées, car elles ne tiennent pas compte des erreurs.

Pour écrire du code à virgule flottante fort, vous devez vous pencher sur la théorie des mesures et savoir comment propager les erreurs de mesure dans vos formules. Cela signifie également que vous devrez remplacer l'égalité de type C (comparaison de bits) par un "égal dans les limites de l'erreur".

Notez que vous ne pouvez pas construire un système qui gère automatiquement l'erreur dans le programme parfaitement, car cela nécessiterait une approche de stockage exacte de taille finie pour tout nombre fractionnaire de chiffres répétitifs éventuellement infinis. En conséquence, l'estimation d'erreur est généralement utilisée, et le résultat est généralement comparé dans une limite d'approximation qui est ajustée pour les valeurs impliquées.

Il ne faut pas longtemps pour se rendre compte que même si votre programme est correct, vous ne pouvez pas faire confiance à la technique car la technique, en général, ne retournera pas les bonnes valeurs.


5 commentaires

L'erreur de représentation de 0,3 n'est PAS égale à la somme des erreurs de représentation de 0,1 et 0,2. Il faut une autre erreur, l'erreur pour représenter la somme des deux approximations. Les erreurs pour représenter 0,1 0,2 et 0,3 en flottant simple précision sont respectivement 1 / (5 * 2 ^ 27), 2 / (5 * 2 ^ 27) et 1 / (5 * 2 ^ 24). L'erreur de représentation (0,1f + 0,2f) est en effet 1 / (2 ^ 27) ...


Une illustration typique est 0,1 + 0,4 == 0,5 en double précision, bien que les deux opérandes aient des erreurs de représentation (les deux par excès), leur somme semble égale à 1/2, ce qui n'a aucune erreur de représentation.


@ aka.nice Je ne pensais pas à l'erreur en termes de grandeur, mais en termes absolus de distance par rapport au nombre idéal. Ainsi, si une erreur est positive et l'autre négative, et que le résultat final peut être obtenu sans aucune erreur, les erreurs positives et négatives doivent être égales pour obtenir le résultat final. J'espère que cela clarifie ma déclaration, mais en général (a + a.error) + (b + b.error) == (c + c.error) quand (a + b == c) signifie que (a.error + b .error = c.error) Sinon, ce n'est pas un système linéaire.


Attention, il s'agit ici d'addition en virgule flottante qui peut comporter son propre arrondi à la fin. Bien que 0,1 et 0,4 soient tous deux supérieurs à 1/10 et 4/10 respectivement, ils totalisent 0,5 après arrondissement à la virgule flottante la plus proche ...


La formulation est donc (a + a.error) + (b + b.error) + sum.error == (c + c.error) Oublier sum.error conduira à une erreur de compréhension.