Pourquoi un float converti en int est-il arrondi en C?

Dans une interview, on m’a demandé ce que je pensais du code suivant:

#include  int main() { float f = 10.7; int a; a = f; printf ("%d\n", a); } 

J’ai répondu:

  • Le compilateur émettra un avertissement lorsque vous modifiez un float en int sans casting.

  • Le int aura la valeur de garbage comme vous n’utilisez pas un cast.

Ensuite, ils m’ont permis d’exécuter le programme sur un compilateur en ligne. J’étais dépassé. Mes deux suppositions étaient fausses. Le compilateur n’a émis aucun avertissement, et l’ int avait la valeur 10. Même quand j’ai changé, le float à une valeur comme 10.9 ou 10.3, la réponse était la même. Même mettre un cast ne change pas le résultat.

Quelqu’un peut-il me dire pourquoi cela se produit et dans quels cas le résultat sera-t-il différent?

NOTE: Pendant la compilation, l’intervieweur m’a dit de ne pas append de drapeaux gcc .


EDIT: Maintenant, j’ai compris que le flotteur n’est pas arrondi et que la réponse sera 10. Mais quelqu’un peut-il m’expliquer pourquoi cela est-il conçu comme ceci? Pourquoi un float converti en int est-il arrondi en dessous? Y a-t-il une raison spécifique?

Voici ce que dit la norme 6.3.1.4:

Lorsqu’une valeur finie de type réel flottant est convertie en un type entier autre que _Bool, la partie fractionnaire est ignorée (en d’autres termes, la valeur est tronquée à zéro). Si la valeur de la partie intégrale ne peut pas être représentée par le type entier, le comportement est indéfini.

La partie comportement indéfini concerne la taille et la signature du nombre entier. Si vous avez choisi un nombre entier trop petit pour contenir le résultat, il appelle un comportement indéfini.

Le int aura la valeur de garbage car vous n’utilisez pas de transt

Int peut avoir une valeur de vidage en cas de débordement, mais la présence ou l’absence de conversion n’a rien à voir avec cela.

Un compilateur n’est jamais obligé de montrer un “avertissement”, les avertissements ne sont pas spécifiés par le standard C, qui ne parle que de diagnostics (c’est-à-dire une sorte de message, appelez-le erreur ou avertissement). Par conséquent, vous ne pouvez jamais présumer que chaque compilateur donnera un avertissement de quelque nature que ce soit.

Dans ce cas de conversion implicite float-to-int, le compilateur n’est pas tenu d’afficher une forme quelconque de diagnostic. Les bons compilateurs le seront cependant.

Dans le cas de GCC, de tels avertissements sont plutôt bâclés, vous devez lui dire explicitement de donner les avertissements en ajoutant -Wconversion ou -Wfloat-conversion . Ce sont les drapeaux supplémentaires auxquels il est fait allusion.

De nombreuses conversions sont implicites en C:

  • entre tous les types numériques,
  • entre les types de pointeurs de données et le type void * ,
  • d’un type de tableau au type de pointeur correspondant (appelé désintégration)
  • d’un type non const à son équivalent const,

Les compilateurs n’émettent généralement pas de diagnostics à ce sujet, car le comportement est défini par la norme, mais certaines de ces conversions indiquent une erreur de programmation, telle que double x = 1 / 2; . Pour aider les programmeurs à éviter ces erreurs stupides, le compilateur peut être invité à émettre des avertissements, voire des erreurs.

Il est sage d’utiliser ces avertissements supplémentaires. gcc fera s’il est -Wall -Wextra -Wconversion -Wfloat-conversion et clang a des parameters similaires: clang -Wall -Weverything . Votre première hypothèse n’était pas irréaliste, mais gcc est notoirement clément avec un code peu soigné par défaut et vous a demandé de ne pas utiliser d’indicateur. Pire encore: pour restr compatible avec les anciens Makefiles, gcc utilise par défaut c89 et se plaindra de l’utilisation de certaines fonctionnalités de c99.

Notez que le langage définit le comportement lorsqu’il peut déterminer qu’ils sont nécessaires, mais parfois il ne le peut pas:

 float f = 10.7; printf("%d\n", f); 

Dans ce cas, la norme spécifie que f doit être transmis à la fonction variadic printf tant que double , mais printf attend un argument int pour le spécificateur %d . Votre deuxième hypothèse serait correcte ici, une conversion explicite avec une conversion (int) est requirejse. Encore une fois, les bons compilateurs peuvent émettre un diagnostic pour ces erreurs s’ils en sont avisés.

De plus, certaines conversions implicites peuvent être déterminées au moment de la compilation pour perdre des informations même sur un comportement indéfini, telles que:

 char x = 300; int x = 1e99; 

Cela aiderait si le compilateur émettait des diagnostics pour ceux-ci même sans option ssortingcte.

Enfin, certaines conversions perdront des informations mais sont plus difficiles à détecter dans le cas général:

 double f = 10000000000000; char a = f; float f = d; int i = d; 

Le langage ne définit le comportement que si le type de réception est suffisamment grand pour la partie intégrale, sinon le comportement n’est pas défini, même avec une conversion explicite. Que le programmeur veuille un avertissement ou non dans ces cas-là est une question de choix personnel.

En ce qui concerne la raison pour laquelle la conversion d’un type à virgule flottante en un type entier est définie pour être arrondie à 0 , cela peut être plus simple, et vous pouvez obtenir d’autres comportements en utilisant round() ou en ajoutant 0.5 avant la conversion si vous connaissez la valeur. est positif.

Il y aura un avertissement en fonction des parameters du compilateur.

Mais selon la norme de langage C, le comportement est absolument bien défini et la valeur en virgule flottante sera arrondie à zéro. a est garanti à être de 10. Comme votre réponse indique faussement que presque tous les programmes C dans le monde sont irrémédiablement brisés, je ne vous engagerais pas pour un travail en C.

C a une conversion définie entre presque tout, ainsi, pratiquement aucune affectation ne produit d’avertissements ou d’erreurs à moins que vous n’activiez des contrôles supplémentaires non requirejs par la spécification.

Pour ce qui est de savoir pourquoi il arrondit à zéro, eh bien, s’il ajoute (ou soustrait) 0,5 avant la troncature afin d’arrondir, alors vous devrez l’annuler avec un code ouvert si ce que vous vouliez était une troncature, afin de ne pas être pire off que si vous devez append 0,5 pour arrondir.

De plus, C est un langage pratique et il était important que son comportement normal corresponde aux instructions disponibles sur les machines actuelles de son époque.

Je pense que votre raisonnement est parfaitement logique. C, cependant, n’a généralement pas de sens; les valeurs en virgule flottante sont implicitement arrondies à zéro. Vous pouvez demander au compilateur d’émettre un avertissement lorsque les types sont implicitement convertis de la sorte.

 /*test.c*/ #include  int main() { double f; int a; f = 10.7; a = f; printf("%d\n", a); return 0; } 

Avec GCC, l’option que nous recherchons est -Wconversion :

 $ c89 -pedantic -Wall -Wconversion test.c test.c: In function 'main': test.c:9:6: warning: conversion to 'int' from 'double' may alter its value [-Wfloat-conversion] a = f; ^