45 votes

Utilisation de "double" comme variables de compteur dans les boucles

Dans un livre que je lis actuellement, il y a cet extrait :

Y comme compteur de boucle. Voici un exemple exemple d'un for l de compteur :

double a(0.3), b(2.5);
for(double x = 0.0; x <= 2.0; x += 0.25)
    cout << "\n\tx = " << x << "\ta*x + b = " << a*x + b;

T valeur de a*x+b pour des valeurs de x de 0.0 a 2.0 , 0.25 ; h l'utilisation d'un compteur à virgule flottante dans une boucle. De nombreuses valeurs décimales ne peuvent pas être représentées exactement sous forme binaire binaire à virgule flottante, et les écarts peuvent s'accumuler avec les valeurs cumulatives. Cela signifie que vous ne devez pas coder une boucle for de telle sorte que la fin de la boucle dépend d'un compteur de boucle à virgule flottante atteigne une valeur précise. Pour exemple, la boucle suivante, mal conçue, ne se suivante, mal conçue, ne se termine jamais :

for(double x = 0.0 ; x != 1.0 ; x += 0.2)
    cout << x;

L'objectif de cette boucle est de sortir la valeur de x a de 0.0 a 1.0 Cependant, 0.2 h valeur binaire à virgule flottante, donc la valeur de x n'est jamais exactement 1 . [ ] est toujours fausse, et l'expression boucle continue indéfiniment.

Quelqu'un peut-il expliquer comment le premier bloc de code fonctionne alors que le second ne fonctionne pas ?

71voto

Jon Skeet Points 692016

Le premier se terminera éventuellement, même si x n'atteint pas exactement 2.0... parce que ça va finir par être plus grand que 2.0, et donc d'éclater.

Le deuxième devrait faire x touchez exactement 1.0 afin de se casser.

Il est regrettable que le premier exemple utilise un pas de 0,25, qui est exactement représentable en virgule flottante binaire - il aurait été plus intelligent que les deux exemples utilisent 0,2 comme taille de pas. (0,2 n'est pas exactement représentable en virgule flottante binaire).

15voto

SLaks Points 391154

Le premier bloc utilise une condition moins que ou égale ( <= ).

Même avec une imprécision en virgule flottante, cela finira par être faux.

9voto

Steve Townsend Points 36948

Il s'agit d'un exemple d'un problème plus large : lorsque vous comparez des doubles, vous devez souvent vérifier l'égalité. dans une tolérance acceptable plutôt qu'une égalité exacte.

Dans certains cas, généralement la vérification d'une valeur par défaut inchangée, l'égalité est parfaite :

double x(0.0); 
// do some work that may or may not set up x

if (x != 0.0) {   
    // do more work 
}

En général, cependant, la vérification par rapport à une valeur attendue ne peut pas être effectuée de cette manière - vous auriez besoin de quelque chose du genre :

double x(0.0); 
double target(10000.0);
double tolerance(0.000001);
// do some work that may or may not set up x to an expected value

if (fabs(target - x) < tolerance) {   
    // do more work 
}

6voto

Jack V. Points 992

Les nombres à virgule flottante sont représentés en interne comme un nombre binaire, presque toujours au format IEEE. Vous pouvez voir comment les nombres sont représentés ici :

http://babbage.cs.qc.edu/IEEE-754/

Par exemple, 0,25 en binaire correspond à 0,01. b et est représenté par +1.00000000000000000000000 * 2 -2 .

Il est stocké en interne avec 1 bit pour le signe, 8 bits pour l'exposant (qui représente une valeur comprise entre -127 et +128, et 23 bits pour la valeur (le 1 de tête n'est pas stocké). En fait, les bits sont :

[0][01111101][00000000000000000000000]

Alors que 0,2 en binaire n'a pas de représentation exacte, tout comme 1/3 n'a pas de représentation exacte en décimal.

Ici, le problème est que, de même que 1/2 peut être représenté exactement en format décimal comme 0,5, mais 1/3 ne peut être qu'approximé à 0,3333333333, 0,25 peut être représenté exactement comme une fraction binaire, mais pas 0,2. En binaire, c'est 0,0010011001100110011001100..... b où les quatre derniers chiffres se répètent.

Pour être stocké sur un ordinateur, il est évalué à 0,001001100110011001100110101. b . Ce qui est très, très proche, donc si vous calculez des coordonnées ou quoi que ce soit d'autre où les valeurs absolues comptent, c'est parfait.

Malheureusement, si vous ajoutez cette valeur à elle-même cinq fois, vous obtiendrez 1,00000000000000000000001. b . (Ou, si vous aviez arrondi 0,2 à 0,00100110011001100110011001100 b au lieu de cela, vous obtiendriez 0,11111111111111111111100 b )

De toute façon, si votre condition de boucle est 1.00000000000000000000001 b \==1.00000000000000000000000 b il ne se terminera pas. Si vous utilisez <= à la place, il est possible qu'il s'exécute une fois de plus si la valeur est juste inférieure à la dernière valeur, mais il s'arrêtera.

Il serait possible de créer un format capable de représenter avec précision les petites valeurs décimales (comme toute valeur ne comportant que deux décimales). Elles sont utilisées dans les calculs financiers, etc. Mais les valeurs normales à virgule flottante fonctionnent de la même manière : elles échangent la capacité de représenter quelques petits nombres "faciles" comme 0,2 contre la capacité de représenter une large gamme de manière cohérente.

Il est courant d'éviter d'utiliser un flottant comme compteur de boucle pour cette raison précise, les solutions courantes seraient :

  • Si une itération supplémentaire n'a pas d'importance, utilisez <=
  • Si cela a de l'importance, créez la condition <=1.0001 à la place, ou une autre valeur inférieure à votre incrément, de sorte que les erreurs de 0.0000000000000000000001 n'aient pas d'importance.
  • Utiliser un nombre entier et le diviser par quelque chose pendant la boucle.
  • Utilisez une classe spécialement conçue pour représenter exactement les valeurs fractionnaires.

Il serait possible pour un compilateur d'optimiser une boucle float "=" pour la transformer en ce que vous souhaitez, mais je ne sais pas si cela est autorisé par la norme ou si cela se produit dans la pratique.

2voto

DigitalRoss Points 80400

L'exemple pose de multiples problèmes, et deux choses sont différentes dans les deux cas.

  • Une comparaison impliquant une égalité en virgule flottante nécessite une connaissance experte du domaine, il est donc plus sûr d'utiliser < o > pour les contrôles en boucle.

  • L'incrément de la boucle 0.25 en fait fait ont une représentation exacte

  • L'incrément de la boucle 0.2 fait no ont une représentation exacte

  • Par conséquent, il est possible de vérifier exactement la somme de plusieurs 0.25 (ou 1.0 ) s'incrémente, mais il n'est pas possible de faire correspondre exactement même une seule 0.2 incrément.

Une règle générale est souvent citée : ne font pas de comparaisons d'égalité de nombres à virgule flottante. Bien que ce soit un bon conseil général, lorsqu'il s'agit d'entiers ou d'entiers plus fractions composés de ½ + ¼ ... vous pouvez vous attendre à des représentations exactes.

Et vous avez demandé pourquoi ? La réponse courte est : parce que les fractions sont représentées comme ½ + ¼ ..., la plupart des nombres décimaux n'ont pas de représentations exactes puisqu'ils ne peuvent pas être factorisés en puissances de deux. Cela signifie que les représentations internes de la FP sont de longues chaînes de bits qui vont... rond à une valeur attendue pour la sortie mais pas réellement être exactement cette valeur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X