Comportement étrange lors de la conversion d’un int pour flotter en C

J’ai un doute sur la sortie du programme C suivant. J’ai essayé de le comstackr à l’aide de Visual C ++ 6.0 et de MinGW32 (gcc 3.4.2).

#include  int main() { int x = 2147483647; printf("%f\n", (float)2147483647); printf("%f\n", (float)x); return 0; } 

La sortie est:

 2147483648.000000 2147483647.000000 

Ma question est la suivante: pourquoi les deux lignes sont-elles différentes? Lorsque vous convertissez la valeur entière 2147483647 au format à virgule flottante IEEE 754, il se rapproche de 2147483648.0. Donc, je m’attendais à ce que les deux lignes soient égales à 2147483648.000000.

EDIT : La valeur “2147483647.000000” ne peut pas être une valeur à virgule flottante simple précision, car le nombre 2147483647 ne peut pas être représenté exactement dans le format à virgule flottante simple précision IEEE 754 sans perte de précision.

Dans les deux cas, le code cherche à convertir un type entier en float , puis à double . La double conversion se produit car il s’agit d’une valeur float transmise à une fonction variadique.

Vérifiez votre configuration de FLT_EVAL_METHOD , soupçonnez qu’elle a une valeur de 1 ou 2 (OP rapporte 2 avec au moins un compilateur). Cela permet au compilateur d’évaluer “… les opérations et les constantes float à la plage et à la précision” supérieures à float .

Votre compilateur optimisé (float)x va directement à double arithmétique. Il s’agit d’une amélioration des performances pendant l’exécution.

(float)2147483647 est une conversion de temps de compilation et le compilateur optimisé pour que int float à double précision double car les performances ne sont pas un problème ici.


[Edit2] Il est intéressant de noter que la spécification C11 est plus spécifique que la spécification C99 avec l’ajout de “Sauf pour l’affectation et la conversion …”. Cela implique que les compilateurs C99 autorisaient parfois la conversion directe double l’ int , sans passer d’abord par float et que la modification de C11 avait été modifiée afin d’empêcher clairement le saut d’un casting.

Avec C11 excluant formellement ce comportement, les développeurs modernes ne devraient pas le faire, mais les plus anciens, comme OP, pourraient constituer un bogue par rapport aux normes C11. À moins d’indication contraire d’une autre spécification C99 ou C89, le compilateur semble avoir un comportement acceptable.


[Edit] Prenant ensemble les commentaires de @Keith Thompson, @tmyklebu, @Matt McNabb, le compilateur, même avec un FLT_EVAL_METHOD zéro, devrait produire 2147483648.0... Ainsi, un indicateur d’optimisation du compilateur remplace explicitement le comportement correct ou le compilateur a un bogue corner.


C99dr §5.2.4.2.2 8 Les valeurs des opérations avec des opérandes flottants et des valeurs soumises aux conversions arithmétiques usuelles et des constantes flottantes sont évaluées dans un format dont la plage et la précision peuvent être supérieures à celles requirejses par le type. L’utilisation des formats d’évaluation est caractérisée par la valeur de FLT_EVAL_METHOD définie par l’implémentation:

-1 indéterminable;

0 évaluer toutes les opérations et les constantes juste à la gamme et à la précision du type;

1 évaluer des opérations et des constantes de type float et double à la plage et à la précision du type double , évaluer long double opérations long double et des constantes à la plage et à la précision du type long double »;

2 évaluer toutes les opérations et constantes à la scope et à la précision du type long double .


C11dr §5.2.4.2.2 9 Sauf pour l’affectation et la conversion (qui suppriment toute plage et toute précision supplémentaires), les valeurs fournies par les opérateurs avec des opérandes flottants et les valeurs soumises aux conversions arithmétiques usuelles et les constantes flottantes sont évaluées dans un format et la précision peut être supérieure à celle requirejse par le type. L’utilisation des formats d’évaluation est caractérisée par la valeur définie par l’implémentation de FLT_EVAL_METHOD

-1 (identique à C99)

0 (identique à C99)

1 (identique à C99)

2 (identique à C99)

C’est certainement un bug du compilateur. De la norme C11, nous avons les garanties suivantes (C99 était similaire):

  • Les types ont un ensemble de valeurs représentables (implicites)
  • Toutes les valeurs pouvant être représentées par float sont également par double (6.2.5 / 10)
  • La conversion de float en double ne modifie pas la valeur (6.3.1.5/1)
  • La int de int en float , lorsque la valeur int est dans l’ensemble des valeurs représentables pour float , donne cette valeur.
  • FLT_MAX int en float , lorsque l’amplitude de la valeur int est inférieure à FLT_MAX et que int n’est pas une valeur représentable pour float , le choix de la valeur float FLT_MAX supérieur ou inférieur est sélectionné, et celle sélectionnée est l’implémentation. -défini. (6.3.1.4/2)

Le troisième de ces points garantit que la valeur float fournie à printf n’est pas modifiée par les promotions d’argument par défaut.

Si 2147483647 est représentable dans float , alors (float)x et (float)2147483647 doivent donner 2147483647.000000 .

Si 2147483647 n’est pas représentable dans float , alors (float)x et (float)2147483647 doivent indiquer le (float)2147483647 ou inférieur. Ils ne doivent pas tous les deux faire la même sélection. Toutefois, cela signifie qu’une impression de 2147483647.000000 n’est pas autorisée 1 , chacune d’elles doit être la valeur la plus élevée ou la valeur la plus basse.


1 Eh bien – il est théoriquement possible que le flottant suivant le plus bas soit 2147483646.9999999... Ainsi, lorsque la valeur est affichée avec une précision de 6 chiffres par printf elle est arrondie pour indiquer ce qui a été vu. Mais ce n’est pas le cas dans IEEE754 et vous pouvez facilement expérimenter pour écarter cette possibilité.

Sur le premier printf , la conversion d’entier en float est effectuée par le compilateur. Sur le second, cela est fait par la bibliothèque d’exécution C. Il n’y a pas de raison particulière pour laquelle ils devraient produire des réponses identiques à la limite de leur précision.

Visual C ++ 6.0 a été publié le siècle dernier et je pense qu’il est antérieur au C ++ standard. Il n’est pas surprenant que VC ++ 6.0 présente un comportement défectueux.

Vous remarquerez également que gcc-3.4.2 date de 2004. En effet, vous utilisez un compilateur 32 bits. gcc sur x86 joue assez vite et avec des maths en virgule flottante . Cela peut être techniquement justifié par le standard C si gcc définit FLT_EVAL_METHOD sur quelque chose de différent de zéro.

Certains d’entre vous ont dit que c’était un bug d’optimisation, mais je ne suis pas du tout d’accord. Je pense que c’est une erreur de précision en virgule flottante raisonnable et un bon exemple montrant comment fonctionne le calcul en virgule flottante.

http://ideone.com/Ssw8GR

peut-être que OP pourrait essayer de coller mon programme sur votre ordinateur et essayer de comstackr avec votre compilateur et voir ce qui se passe. ou essayez:

http://ideone.com/OGPBC

(avec conversion flottante explicite).

de toute façon, si nous calculons l’erreur, c’est 4.656612875245797e-10 , et il faut le considérer comme assez précis.

cela pourrait aussi être lié à la préférence de printf .