Valeur entière du type flottant en C

#include<stdio.h>
 int main()
 {
   float a;
   printf("Enter a number:");
   scanf("%f",&a);
   printf("%d",a);
   return 0;
 }

j'exécute le programme avec gcc dans Ubuntu. Pour des valeurs--

          3.3 it gives value 1610612736 
          3.4 it gives value 1073741824
          3.5 it gives value 0
          3.6 it gives value -1073741824
          4 it gives value 0
          5 it gives value 0

qu'est-Ce qui se passe? Pourquoi ces valeurs sont imprimées? Je le fais intentionnellement, mais j'aimerais comprendre pourquoi cela se produit. Les détails sont appréciés!

10
demandé sur pierokr 2011-12-29 22:03:30

7 réponses

printf fonction ne sait pas le type de format que vous avez passé, parce que cette partie est variadique.

int printf(const char* format, ...);
//                             ^^^

dans la norme C, passer un float sera automatiquement promue double (C11§6.5.2.2 / 6), et rien d'autre ne sera fait du côté de l'appelant.

à l'Intérieur printf, car il ne sait pas le type de l' ... thingie (§6.7.6.3/9), il doit utiliser l'indicateur d'ailleurs - la chaîne de format. Puisque vous avez passé "%d", c'est dire à la fonction que, un int est prévu.

selon la norme C, cela conduit à un comportement non défini (§7.21.6.1 / 8-9), qui inclut la possibilité d'imprimer un nombre bizarre, fin de l'histoire.

mais que se passe-t-il vraiment? sur la plupart des plateformes, un double est représenté par" IEEE 754 binary64" format, et un float binary32 format. Les nombres que vous avez entrés sont convertis en float, qui n'a que 23 bits de signification, ce qui signifie que les nombres seront approximés comme ceci:

3.3 ~ (0b1.10100110011001100110011) × 2¹  (actually: 3.2999999523162842...)
3.4 ~ (0b1.10110011001100110011010) × 2¹  (actually: 3.4000000953674316...)
3.5 = (0b1.11                     ) × 2¹  (actually: 3.5)
3.6 ~ (0b1.11001100110011001100110) × 2¹  (actually: 3.5999999046325684...)
4   = (0b1                        ) × 2²  (actually: 4)
5   = (0b1.01                     ) × 2²  (actually: 5)

maintenant nous convertissons ceci en double, qui a 53 bits de signification, que nous devons insérer 30 "0" binaires à la fin de ces nombres, pour produire par exemple

3.299999952316284 = 0b1.10100110011001100110011000000000000000000000000000000 ×2¹

ceux-ci sont principalement pour dériver la représentation réelle de ces nombres, qui sont:

3.3 → 400A6666 60000000
3.4 → 400B3333 40000000
3.5 → 400C0000 00000000
3.6 → 400CCCCC C0000000
4   → 40100000 00000000
5   → 40140000 00000000

je recommande d'utiliser http://www.binaryconvert.com/convert_double.html pour voir comment cela se décompose à l' ± m × 2 e format.

quoi qu'il en soit, je suppose que votre système est un x86/x86_64/ARM en configuration normale, ce qui signifie que les nombres sont disposés en mémoire en utilisant format little-endian, donc les arguments passés seront comme

 byte
  #0   #1   ...          #4   ...            #8 ....
+----+----+----+----+  +----+----+----+----+----+----+----+----+
| 08 | 10 | 02 | 00 |  | 00 | 00 | 00 | 60 | 66 | 66 | 0A | 40 | ....
+----+----+----+----+  +----+----+----+----+----+----+----+----+
 address of "%d"         content of 3.299999952316284
 (just an example)

à l'intérieur du printf, il consomme la chaîne de format "%d", l'analyse, et puis découvre qu'un int est nécessaire à cause de %d, donc 4 octets sont pris à partir de l'entrée variadic, qui est:

 byte
  #0   #1   ...          #4   ...            #8 ....
+ - -+ - -+ - -+ - -+  +====+====+====+====+ - -+ - -+ - -+ - -+
: 08 : 10 : 02 : 00 :  | 00 | 00 | 00 | 60 | 66 : 66 : 0A : 40 : ....
+ - -+ - -+ - -+ - -+  +====+====+====+====+ - -+ - -+ - -+ - -+
 address of "%d"        ~~~~~~~~~~~~~~~~~~~
                        this, as an 'int'

ainsi, printf va recevoir 0x60000000, et l'afficher comme un nombre décimal entier, qui est 1610612736, ce qui est pourquoi vous voyez ce résultat. Les autres nombres peuvent être expliqués de la même façon.

3.3 → ... 60000000 = 1610612736
3.4 → ... 40000000 = 1073741824
3.5 → ... 00000000 = 0
3.6 → ... C0000000 = -1073741824 (note 2's complement)
4   → ... 00000000 = 0
5   → ... 00000000 = 0
22
répondu kennytm 2011-12-29 19:00:35

je suppose que les autres réponses postées jusqu'à présent manquent le point: je pense que vous êtes délibérément en utilisant différentes conversions pour la numérisation et l'impression, et veulent comprendre les résultats. Si, en effet, vous avez juste fait une erreur, alors vous pouvez ignorer ma réponse.

en gros, vous devez lire cet article, qui expliquera comment les patrons de bits pour les nombres à virgule flottante sont définis, puis écrira les patrons de bits pour chacun de ces nombres. Étant donné que vous comprenez comment les entiers sont stockés, vous devriez avoir vos réponses.

2
répondu Ernest Friedman-Hill 2011-12-29 18:06:41

d indicateur de conversion que vous utilisez dans votre deuxième printf déclaration nécessite un argument de type int. Votre argument a après C la promotion d'argument par défaut est de type double. Passer un argument d'un type différent que celui attendu est un comportement non défini et comme d'habitude avec un comportement non défini, tout peut arriver.

0
répondu ouah 2011-12-29 18:14:23

Si vous voulez savoir exactement ce qui se passe, essayez printf('0x%08x\n', a); au lieu de printf("%d",a);. Vous pourrez voir les bits réels de la variable a au lieu de quoi printf("%d",a); est de vous donner.

0
répondu David Pointer 2011-12-29 18:14:55

tout simplement parce que printf ("%d",a); : pense que la mémoire est un int donc il interpréter son contenu comme un int. et printf("%f",a); considérer le contenu de la mémoire de a comme un flotteur qu'il est vraiment...

mais si vous écrivez printf("%d",(int)a); / / A est transformé en int (par (int) moulé avec troncature). ainsi, le approximativ valeur de a est imprimé.

0
répondu Hicham from CppDepend Team 2011-12-29 18:15:29

en C, les flotteurs passés en argument à des fonctions avec un nombre variable d'arguments sont promus en double. C'est pourquoi dans la référence de la chaîne de format de la fonction printf vous ne verrez pas de spécificateurs de format différents pour les flotteurs et les doubles. Ainsi, votre " a " passe d'un flotteur 32 bits à un double 64 bits lorsqu'il est passé à printf. Il se trouve que 4 et 5 sont représentés comme des doubles de telle manière que 32 des 64 bits sont nuls, et ces zéro les bits sont ceux qui la fonction printf interprète comme un entier, puisque vous lui avez dit d'imprimer un entier.

0
répondu Mike Nakis 2011-12-29 18:18:09

printf() interprète son(ses) argument (S) de longueur variable en utilisant les spécificateurs de format qui ont été mentionnés dans le premier paramètre. La signature de printf() est comme suit.

int printf(const char *format, ...);

printf() le code serait probablement écrit comme ceci en utilisant stdarg.h.

int printf(const char *format, ...) {
    va_list ap;
    char *p, *sval;
    int ival;
    float fval;

    va_start(ap, format);
    for(p=format; *p ; p++) {
        if (*p != '%') {
            putchar(*p);
            continue;
        }
        switch(*++p) {
            case 'd':
                ival = va_arg(ap, int);
                break;

            case 'f':
                fval = va_arg(ap, float);
                break;

            case 's':
                for (sval = va_arg(ap, char *); *sval; sval++);
                break;

            default:
                putchar(*p);
                break;
        }
    }
    va_end(ap);
}

Donc si vous passer %dfloat alors vous pouvez comprendre ce qui se passera à l'intérieur de la printf(). printf() interprétera un float variable int et ce comportement est undefined!

J'espère que cela vous aidera!

0
répondu Sangeeth Saravanaraj 2011-12-29 18:26:01