web-dev-qa-db-fra.com

Casting float to int (bitwise) en C

Étant donné les 32 bits qui représentent un nombre à virgule flottante IEEE 754, comment ce nombre peut-il être converti en un entier, en utilisant des opérations entières ou en bits sur la représentation (plutôt que d'utiliser une instruction machine ou une opération du compilateur à convertir)?

J'ai la fonction suivante mais cela échoue dans certains cas:

Entrée: int x (contient un nombre simple précision de 32 bits au format IEEE 754)

  if(x == 0) return x;

  unsigned int signBit = 0;
  unsigned int absX = (unsigned int)x;
  if (x < 0)
  {
      signBit = 0x80000000u;
      absX = (unsigned int)-x;
  }

  unsigned int exponent = 158;
  while ((absX & 0x80000000) == 0)
  {
      exponent--;
      absX <<= 1;
  }

  unsigned int mantissa = absX >> 8;

  unsigned int result = signBit | (exponent << 23) | (mantissa & 0x7fffff);
  printf("\nfor x: %x, result: %x",x,result);
  return result;
12
Anonymous

C a "l'union" pour gérer ce type d'affichage de données:

typedef union {
  int i;
  float f;
 } u;
 u u1;
 u1.f = 45.6789;
 /* now u1.i refers to the int version of the float */
 printf("%d",u1.i);
19
Carl

(Quelqu'un devrait revérifier cette réponse, en particulier les cas-frontières et l’arrondi des valeurs négatives. De plus, je l’ai écrite pour arrondir au plus proche. Pour reproduire la conversion de C, elle devrait être modifiée pour arrondir à zéro.)

Le processus consiste essentiellement à:

Séparez les 32 bits en un bit de signe (s), huit bits d'exposant (e) et 23 bits de significande (f). Nous les traiterons comme des entiers à deux compléments.

Si e est 255, l'objet en virgule flottante est soit l'infini (si f est égal à zéro) ou un NaN (sinon). Dans ce cas, la conversion ne peut pas être effectuée et une erreur doit être signalée.

Sinon, si e n'est pas zéro, ajoutez 224 à f. (Si e n'est pas zéro, le significande a implicitement un 1 bit à son début. Ajoutant 224 rend ce bit explicite dans f.)

Soustrayez 127 de e. (Ceci convertit l’exposant de sa forme biaisée/codée en exposant réel. Si nous effectuions une conversion générale en une valeur quelconque, nous devrions gérer le cas spécial où e est égal à zéro: Soustrayez 126 au lieu de 127. Mais, comme nous ne convertissons que dans un résultat entier, nous pouvons négliger ce cas, tant que le résultat entier est nul pour ces très petits nombres.)

Si s est 0 (le signe est positif) et que e est égal ou supérieur à 31, la valeur déborde du nombre entier signé de 32 bits (il s'agit de 231 ou plus grand). La conversion ne peut pas être effectuée et une erreur doit être signalée.

Si s est 1 (le signe est négatif) et e est supérieur à 31, la valeur déborde d'un entier signé de 32 bits (inférieure ou égale à -2)32). Si s est un, e est 32, et f est supérieur à 224 (l’un des bits significatifs originaux a été défini), la valeur dépasse alors un entier signé de 32 bits (inférieure à -231; si l'original f était égal à zéro, il serait exactement -231, qui ne déborde pas). Dans tous les cas, la conversion ne peut pas être effectuée et une erreur doit être signalée.

Nous avons maintenant un s, un e et un f pour une valeur qui ne déborde pas afin de pouvoir préparer la valeur finale.

Si s vaut 1, définissez f sur -f.

La valeur de l'exposant est pour une signification entre 1 (inclus) et 2 (exclusif), mais notre signification commence par un peu à 224. Nous devons donc nous adapter à cela. Si e est égal à 24, notre signification est correcte et nous avons terminé; nous renvoyons donc f comme résultat. Si e est supérieur à 24 ou inférieur à 24, nous devons déplacer la signification de manière appropriée. De plus, si nous devons décaler f à droite, il se peut que nous devions l'arrondir pour obtenir un résultat arrondi à l'entier le plus proche.

Si e est supérieur à 24, décaler f à gauche e à 24 bits. Renvoie f comme résultat.

Si e est inférieur à -1, le nombre à virgule flottante est compris entre -½ et ½, exclusif. Renvoie 0 comme résultat.

Sinon, nous allons décaler f droite 24 -e bits. Cependant, nous allons d’abord enregistrer les éléments nécessaires pour l’arrondissement. Définissez r sur le résultat obtenu en convertissant f en un entier non signé de 32 bits et en le décalant à gauche de 32- (24 - e) bits (de manière équivalente, à gauche de 8 + ebits). Cela prend les bits qui seront décalés de f (ci-dessous) et les «ajustera à gauche» dans les 32 bits, de sorte que nous avons une position fixe où ils commencent.

Décalage f droite 24 -e bits.

Si r est inférieur à 231, ne faites rien (ceci est arrondi vers le bas; le décalage des bits tronqués). Si r est supérieur à 231, en ajoute un à f (ceci est arrondi). Si r est égal à 231, ajoutez le bit faible de f à f. (Si f est impair, ajoutez-en un à f. Des deux valeurs proches, cela arrondit à la valeur paire.) Renvoie f.

7
Eric Postpischil

&x donne l'adresse de x donc a float* type.

(int*)&x convertit ce pointeur en un pointeur vers int, c’est-à-dire en une chose int*.

*(int*)&x déréférence ce pointeur dans une valeur int. Cela ne fera pas ce que vous croyez sur des machines où int et float ont des tailles différentes. 

Et il pourrait y avoir des problèmes d'endianité.

Cette solution a été utilisée dans l’algorithme fast root square .

7
float x = 43.133;
int y;

assert (sizeof x == sizeof y);
memcpy (&y, &x, sizeof x);
...
1
wildplasser

Vous ne pouvez pas (de manière significative) convertir un nombre à virgule flottante en un 'entier' (signed int ou int) de cette manière.

Il se peut qu’il finisse par avoir le type entier, mais il s’agit en fait d’un index dans l’espace de codage IEEE754, et non d’une valeur significative en soi.

Vous pourriez peut-être affirmer que unsigned int a un double objectif, à savoir un motif binaire et une valeur entière, mais pas int.


Il y a aussi problèmes de plate-forme avec la manipulation de bits des entiers signés.

1
Alex Brown
// With the proviso that your compiler implementation uses
// the same number of bytes for an int as for a float:
// example float
float f = 1.234f;
// get address of float, cast as pointer to int, reference
int i = *((int *)&f);
// get address of int, cast as pointer to float, reference
float g = *((float *)&i);
printf("%f %f %08x\n",f,g,i);
0
Dino Dini