Comme @Angew l'a souligné , l' !=
opérateur a besoin du même type des deux côtés.
(float)i != i
se traduit également par la promotion de la RHS à flotter, c'est ce que nous avons fait (float)i != (float)i
.
g ++ génère également une boucle infinie, mais il n'optimise pas le travail de l'intérieur. Vous pouvez voir qu'il convertit int-> float avec cvtsi2ss
et ucomiss xmm0,xmm0
se compare (float)i
avec lui-même. (C'était votre premier indice que votre source C ++ ne signifie pas ce que vous pensiez qu'elle faisait, comme l'explique la réponse de @ Angew.)
x != x
est seulement vrai quand il est "non ordonné" parce que x
c'était NaN. (se INFINITY
compare égal à lui-même en mathématiques IEEE, mais NaN ne le fait pas. NAN == NAN
est faux, NAN != NAN
est vrai).
gcc7.4 et les versions antérieures optimisent correctement votre code en jnp
tant que branche de boucle ( https://godbolt.org/z/fyOhW1 ): continuez à boucler tant que les opérandes x != x
ne sont pas NaN. (gcc8 et les versions ultérieures vérifient également je
une rupture de la boucle, échouant à l'optimisation en se basant sur le fait que ce sera toujours vrai pour toute entrée non-NaN). x86 FP compare l'ensemble PF sur non ordonné.
Et BTW, cela signifie que l'optimisation de clang est également sûre : il suffit de CSE (float)i != (implicit conversion to float)i
comme étant le même, et de prouver que ce i -> float
n'est jamais NaN pour la plage possible de int
.
(Bien que cette boucle atteigne UB de débordement signé, il est autorisé à émettre littéralement n'importe quel asm qu'il veut, y compris une ud2
instruction illégale, ou une boucle infinie vide quel que soit le corps de la boucle.) Mais en ignorant le dépassement signé UB , cette optimisation est toujours légale à 100%.
GCC ne parvient pas à optimiser le corps de la boucle, même si le -fwrapv
débordement d'entier signé est bien défini (en tant que complément à 2). https://godbolt.org/z/t9A8t_
Même l'activation -fno-trapping-math
n'aide pas. (La valeur par défaut de GCC est malheureusement d'activer
-ftrapping-math
même si l'implémentation de GCC est cassée / boguée .) La conversion int-> float peut provoquer une exception FP inexacte (pour des nombres trop grands pour être représentés exactement), donc avec des exceptions éventuellement démasquées, il est raisonnable de ne pas optimisez le corps de la boucle. (Parce que la conversion 16777217
en float pourrait avoir un effet secondaire observable si l'exception inexacte est démasquée.)
Mais avec -O3 -fwrapv -fno-trapping-math
, c'est une optimisation ratée à 100% de ne pas compiler cela en une boucle infinie vide. Sans #pragma STDC FENV_ACCESS ON
, l'état des indicateurs persistants qui enregistrent les exceptions FP masquées n'est pas un effet secondaire observable du code. Non int
-> la float
conversion peut entraîner NaN, donc x != x
cela ne peut pas être vrai.
Ces compilateurs sont tous optimisés pour les implémentations C ++ qui utilisent IEEE 754 simple précision (binary32) float
et 32 bits int
.
La boucle corrigée d'(int)(float)i != i
un bogue aurait UB sur les implémentations C ++ avec 16 bits étroit int
et / ou plus large float
, car vous auriez atteint un débordement d'entier signé UB avant d'atteindre le premier entier qui n'était pas exactement représentable en tant que float
.
Mais UB sous un ensemble différent de choix définis par l'implémentation n'a pas de conséquences négatives lors de la compilation pour une implémentation comme gcc ou clang avec l'ABI x86-64 System V.
BTW, vous pouvez calculer statiquement le résultat de cette boucle à partir de FLT_RADIX
et FLT_MANT_DIG
, défini dans <climits>
. Ou du moins vous pouvez en théorie, si float
correspond réellement au modèle d'un flotteur IEEE plutôt qu'à un autre type de représentation en nombre réel comme un Posit / unum.
Je ne sais pas à quel point la norme ISO C ++ résout le float
comportement et si un format qui n'était pas basé sur des champs d'exposant et de significand à largeur fixe serait conforme aux normes.
Dans les commentaires:
@geza Je serais intéressé d'entendre le nombre résultant!
@nada: c'est 16777216
Êtes-vous en train de prétendre que vous avez cette boucle à imprimer / renvoyer 16777216
?
Mise à jour: puisque ce commentaire a été supprimé, je ne pense pas. Il est probable que l'OP ne cite que l' float
avant du premier entier qui ne peut pas être exactement représenté sous forme de 32 bits float
. https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limits_on_integer_values c'est à dire ce qu'ils espéraient vérifier avec ce code bogué.
La version corrigée du bogue afficherait bien sûr 16777217
, le premier entier qui n'est pas exactement représentable, plutôt que la valeur avant cela.
(Toutes les valeurs flottantes supérieures sont des entiers exacts, mais ce sont des multiples de 2, puis 4, puis 8, etc. pour les valeurs d'exposant supérieures à la largeur du significand. De nombreuses valeurs entières plus élevées peuvent être représentées, mais 1 unité à la dernière place (du significande) est supérieur à 1, donc ce ne sont pas des entiers contigus. Le plus grand fini float
est juste en dessous de 2 ^ 128, ce qui est trop grand pour pair int64_t
.)
Si un compilateur sortait de la boucle d'origine et l'affichait, ce serait un bogue du compilateur.