55 votes

Utilisation de la notation scientifique dans les boucles for

Je suis récemment tombé sur un code qui comporte une boucle de la forme suivante

for (int i = 0; i < 1e7; i++){
}

Je me demande s'il est judicieux de faire cela car 1e7 est un type à virgule flottante, et causera i à promouvoir lors de l'évaluation de la condition d'arrêt. Cela doit-il être une source d'inquiétude ?

50voto

Bathsheba Points 23209

Le site l'éléphant dans la pièce ici est que la portée d'un int pourrait peuvent être aussi petites que -32767 à +32767, et le comportement consistant à attribuer une valeur plus grande que celle-ci à une telle valeur n'a pas été pris en compte. int est indéfini .

Mais, en ce qui concerne votre point principal, en effet, il devrait vous concerner car il s'agit d'une très mauvaise habitude. Les choses pourraient mal tourner car oui, 1e7 est un type de double à virgule flottante.

Le fait que i sera converti en virgule flottante en raison des règles de promotion de type est quelque peu discutable : le véritable dommage est causé par un changement inattendu de la valeur de l'option troncature de l'intégrale littérale apparente. A titre de "preuve par l'exemple", considérons d'abord la boucle

for (std::uint64_t i = std::numeric_limits<std::uint64_t>::max() - 1024; i ++< 18446744073709551615ULL; ){
    std::cout << i << "\n";
}

Cela produit chaque valeur consécutive de i dans la gamme, comme on peut s'y attendre. Notez que std::numeric_limits<std::uint64_t>::max() est 18446744073709551615ULL ce qui est 1 de moins que la 64ème puissance de 2. (Ici, j'utilise un "opérateur" de type toboggan. ++< ce qui est utile lorsque l'on travaille avec unsigned types. De nombreuses personnes considèrent --> et ++< comme obscurcissant, mais dans la programmation scientifique, ils sont communs, en particulier --> .)

Maintenant, sur ma machine, un double est une virgule flottante IEEE754 de 64 bits. (Ce schéma est particulièrement bon pour représenter les puissances de 2 exactement - IEEE754 peut représenter les puissances de 2 jusqu'à 1022 exactement). Donc 18,446,744,073,709,551,616 (la 64e puissance de 2) peut être représenté exactement comme un double. Le nombre représentable le plus proche avant cela est 18,446,744,073,709,550,592 (qui est de 1024 moins).

Alors maintenant, écrivons la boucle comme suit

for (std::uint64_t i = std::numeric_limits<std::uint64_t>::max() - 1024; i ++< 1.8446744073709551615e19; ){
    std::cout << i << "\n";         
}

Sur ma machine, cela ne donne que un valeur de i : 18,446,744,073,709,550,592 (le nombre que nous avons déjà vu). Cela prouve que 1.8446744073709551615e19 est un type à virgule flottante. Si le compilateur était autorisé à traiter le littéral comme un type intégral, les résultats des deux boucles seraient équivalents.

14voto

Frank Puffer Points 5064

Cela fonctionnera, en supposant que votre int est au moins de 32 bits.

Cependant, si vous voulez vraiment utiliser la notation exponentielle, il vaut mieux définir une constante entière en dehors de la boucle et utiliser un casting approprié, comme ceci :

const int MAX_INDEX = static_cast<int>(1.0e7);
...
for (int i = 0; i < MAX_INDEX; i++) {
    ...
}

Compte tenu de cela, je dirais qu'il est bien mieux d'écrire

const int  MAX_INDEX = 10000000;

ou si vous pouvez utiliser C++14

const int  MAX_INDEX = 10'000'000;

10voto

1e7 est un littéral de type double et généralement double est au format 64 bits IEEE 754 avec une mantisse de 52 bits. En gros, chaque dixième puissance de 2 correspond à une troisième puissance de 10, donc double devrait être capable de représenter des nombres entiers jusqu'à 10 au moins. 5*3 \= 10 15 , exactement . Et si int est de 32 bits, alors int a environ 10 3*3 \= 10 9 comme valeur maximale (en cherchant sur Google, on trouve que "2**31 - 1" = 2 147 483 647, c'est-à-dire le double de l'estimation approximative).

Donc, en pratique, il est sûr sur les systèmes de bureau actuels et plus grands.

Mais le C++ permet int n'est que de 16 bits, et sur un système embarqué aussi petit, par exemple. int on aurait un comportement indéfini.

2voto

datenwolf Points 85093

Si l'intention est de boucler pendant un nombre entier exact d'itérations, par exemple si l'on itère sur tous les éléments d'un tableau, la comparaison avec une valeur à virgule flottante n'est peut-être pas une bonne idée, uniquement pour des raisons de précision ; étant donné que la conversion implicite d'un entier en flottant tronquera les entiers vers zéro, il n'y a pas de réel danger d'accès hors limites, la boucle sera simplement interrompue.

La question est maintenant de savoir quand ces effets se manifestent réellement. Votre programme les ressentira-t-il ? La représentation en virgule flottante généralement utilisée de nos jours est IEEE 754. Tant que l'exposant est égal à 0, une valeur en virgule flottante est essentiellement un nombre entier. La double précision C utilise 52 bits pour la mantisse, ce qui vous donne une précision entière jusqu'à 2^52, ce qui est de l'ordre d'environ 1e15. Sans spécifier avec un suffixe f si vous voulez qu'un littéral en virgule flottante soit interprété en simple précision, le littéral sera en double précision et la conversion implicite le ciblera également. Donc, tant que la condition de fin de boucle est inférieure à 2^52, cela fonctionnera. de manière fiable !

Une question à laquelle vous devez réfléchir sur l'architecture x86 est l'efficacité. Les toutes premières FPU 80x87 ont été livrées dans un emballage différent, et plus tard dans une puce différente. Par conséquent, l'introduction de valeurs dans les registres de la FPU est un peu difficile au niveau de l'assemblage x86. Selon vos intentions, cela peut faire la différence dans le temps d'exécution d'une application en temps réel, mais c'est une optimisation prématurée.

TL;DR : Est-ce que c'est sans danger ? Très certainement oui. Cela va-t-il causer des problèmes ? Il pourrait causer des problèmes numériques. Peut-elle invoquer un comportement non défini ? Cela dépend de la façon dont vous utilisez la condition de fin de boucle, mais si i est utilisé pour indexer un tableau et que, pour une raison quelconque, la longueur du tableau se retrouve dans une variable à virgule flottante toujours tronquée vers zéro, cela ne va pas causer de problème logique. Est-ce une chose intelligente à faire ? Cela dépend de l'application.

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