Comment obtenir des octets float?

J’utilise HIDAPI pour envoyer des données à un périphérique USB. Ces données ne peuvent être envoyées que sous forme de tableau d’octets et j’ai besoin d’envoyer des nombres flottants à l’intérieur de ce tableau de données. Je sais que les flotteurs ont 4 octets alors j’ai pensé que cela pourrait fonctionner:

float f = 0.6; char data[4]; data[0] = (int) f >> 24; data[1] = (int) f >> 16; data[2] = (int) f >> 8; data[3] = (int) f; 

Et plus tard, tout ce que je devais faire, c’était:

 g = (float)((data[0] << 24) | (data[1] << 16) | (data[2] << 8) | (data[3]) ); 

Mais tester cela me montre que les lignes comme data[0] = (int) f >> 24; renvoie toujours 0 . Qu’est-ce qui ne va pas dans mon code et comment puis-je le faire correctement (par exemple, casser une donnée interne de float dans 4 octets de caractères et reconstruire le même float ultérieurement)?

MODIFIER:

J’ai pu accomplir cela avec les codes suivants:

 float f = 0.1; unsigned char *pc; pc = (unsigned char*)&f; // 0.6 in float pc[0] = 0x9A; pc[1] = 0x99; pc[2] = 0x19; pc[3] = 0x3F; std::cout << f << std::endl; // will print 0.6 

et

 *(unsigned int*)&f = (0x3F << 24) | (0x19 << 16) | (0x99 << 8) | (0x9A << 0); 

Je sais que memcpy () est une manière “plus propre” de le faire, mais de cette façon, je pense que les performances sont un peu meilleures.

Vous pouvez le faire comme ça:

 char data[sizeof(float)]; float f = 0.6f; memcpy(data, &f, sizeof f); // send data float g; memcpy(&g, data, sizeof g); // receive data 

Pour que cela fonctionne, les deux machines doivent utiliser les mêmes représentations en virgule flottante.


Comme cela a été souligné à juste titre dans les commentaires, vous n’avez pas nécessairement besoin de faire de la memcpy supplémentaire; au lieu de cela, vous pouvez traiter f directement comme un tableau de caractères (de toute signature). Cependant, vous devez encore vous memcpy du côté destinataire, car vous ne pouvez pas traiter un tableau arbitraire de caractères comme un nombre flottant! Exemple:

 unsigned char const * const p = (unsigned char const *)&f; for (size_t i = 0; i != sizeof f; ++i) { printf("Byte %zu is %02X\n", i, p[i]); send_over_network(p[i]); } 

En standard, C est garanti que tout type peut être accédé en tant que tableau d’octets. Une façon simple de le faire est, bien sûr, d’utiliser des syndicats:

  #include  int main(void) { float x = 0x1.0p-3; /* 2^(-3) in hexa */ union float_bytes { float val; unsigned char bytes[sizeof(float)]; } data; data.val = x; for (int i = 0; i < sizeof(float); i++) printf("Byte %d: %.2x\n", i, data.bytes[i]); data.val *= 2; /* Doing something with the float value */ x = data.val; /* Retrieving the float value */ printf("%.4f\n", data.val); getchar(); } 

Comme vous pouvez le constater, il n’est pas du tout nécessaire d’utiliser memcpy ou des pointeurs ...

L’approche union est facile à comprendre, standard et rapide.

MODIFIER.

Je vais expliquer pourquoi cette approche est valide en C ( C99 ).

  • [5.2.4.2.1 (1)] Un octet a des bits CHAR_BIT (une constante entière> = 8, dans la plupart des cas CHAR_BIT 8).
  • [6.2.6.1 (3)] Le type unsigned char utilise tous ses bits pour représenter la valeur de l'object, qui est un entier non négatif, dans une représentation binary pure. Cela signifie qu'il n'y a pas de bits de remplissage ni de bits utilisés pour un autre objective d'extrusion. (La même chose n'est pas garantie pour les caractères ou les types de caractères signed char ).
  • [6.2.6.1 (2)] Chaque type de champ non binary est représenté en mémoire sous la forme d'une séquence d'octets contigus.
  • [6.2.6.1 (4)] (Cité) "Les valeurs stockées dans des objects autres que des champs de bits d'un autre type d'object se composent de n × bits CHAR_BIT, n étant la taille en octets d'un object de ce type. La valeur peut être copié dans un object de type unsigned char [n] (par exemple, par mémoire); [...] "
  • [6.7.2.1 (14)] Un pointeur sur un object de structure (en particulier, des unions), converti de manière appropriée, pointe vers son membre initial. (Ainsi, il n'y a pas d'octets de remplissage au début d'une union).
  • [6.5 (7)] Le contenu d'un object est accessible par un type de caractère:

Un object doit avoir sa valeur stockée accessible uniquement par une expression lvalue qui possède l'un des types suivants:
- un type compatible avec le type effectif de l'object,
- une version quali fi ée d'un type compatible avec le type effectif de l'object,
- un type qui est le type signé ou non signé correspondant au type effectif de l'object,
- un type qui est le type signé ou non signé correspondant à une version quali fi ée du type effectif de l'object,
- un type d'agrégat ou d'union qui inclut l'un des types susmentionnés parmi ses membres (y compris, de manière récursive, un membre d'une union sous-agrégée ou confinée), ou
- un type de caractère

Plus d'information:

Une discussion dans les groupes Google
Le typage

EDIT 2

Un autre détail de la norme C99:

  • [6.5.2.3 (3) Note de bas de page 82] Le typage est autorisé:

Si le membre utilisé pour accéder au contenu d'un object d'union n'est pas identique au dernier membre utilisé pour stocker une valeur dans l'object, la partie appropriée de la représentation de l'object de la valeur est réinterprétée en tant que représentation d'object dans le nouveau type décrit au 6.2.6 (un processus parfois appelé "type punning"). Cela pourrait être une représentation de piège.

Le langage C garantit que toute valeur de n’importe quel type¹ peut être consultée sous forme de tableau d’octets. Le type d’octets est unsigned char . Voici un moyen simple de copier un float dans un tableau d’octets. sizeof(f) est le nombre d’octets utilisés pour stocker la valeur de la variable f ; vous pouvez également utiliser sizeof(float) (vous pouvez soit passer sizeof une expression variable ou plus complexe, ou son type).

 float f = 0.6; unsigned char data[sizeof(float)]; size_t i; for (i = 0; i < sizeof(float); i++) { data[i] = (unsigned char*)f + i; } 

Les fonctions memcpy ou memmove font exactement cela (ou une version optimisée de celles-ci).

 float f = 0.6; unsigned char data[sizeof(float)]; memcpy(data, f, sizeof(f)); 

Vous n'avez même pas besoin de faire cette copie, cependant. Vous pouvez directement passer un pointeur sur le float à votre fonction d'écriture sur USB et lui indiquer le nombre d'octets à copier ( sizeof(f) ). Vous aurez besoin d'une conversion explicite si la fonction prend un argument de pointeur autre que void* .

 int write_to_usb(unsigned char *ptr, size_t size); result = write_to_usb((unsigned char*)f, sizeof(f)) 

Notez que cela ne fonctionnera que si le périphérique utilise la même représentation des nombres à virgule flottante, commune mais non universelle. La plupart des machines utilisent les formats à virgule flottante IEEE , mais vous devrez peut-être changer de finalité.


En ce qui concerne votre tentative, l’opérateur >> opère sur des entiers. Dans l'expression (int) f >> 24 , f est converti en un int ; Si vous aviez écrit f >> 24 sans la conversion, f serait toujours automatiquement converti en un int . La conversion d'une valeur à virgule flottante en entier est une approximation en la tronquant ou en l'arrondissant (généralement vers 0, mais la règle dépend de la plate-forme). 0.6 arrondi à un entier vaut 0 ou 1, donc data[0] vaut 0 ou 1 et les autres sont tous égaux à 0.

Vous devez agir sur les octets de l'object float et non sur sa valeur.

¹ Exclut les fonctions qui ne peuvent pas vraiment être manipulées en C, mais en incluant les pointeurs de fonction qui s’affaiblissent automatiquement.

En supposant que les deux périphériques aient la même notion de la façon dont les flottants sont représentés, pourquoi ne pas simplement en faire une memcpy . c’est à dire

 unsigned char payload[4]; memcpy(payload, &f, 4); 

Le moyen le plus sûr de le faire, si vous contrôlez les deux côtés, est d’envoyer une sorte de représentation normalisée … ce n’est pas le plus efficace, mais ce n’est pas si mal pour les petits nombres.

 hostPort writes char * "34.56\0" byte by byte client reads char * "34.56\0" 

convertit ensuite en float avec la fonction de bibliothèque atof ou atof_l .

Bien sûr, ce n’est pas le plus optimisé, mais il sera facile de déboguer.

si vous voulez être plus optimisé et plus créatif, le premier octet est la longueur, puis l’exposant, puis chaque octet représente 2 décimales … alors

34.56 devient char array[] = {4,-2,34,56}; Quelque chose comme ça serait portable … J’essayerais simplement de ne pas passer de représentations flottantes binarys … parce que cela peut devenir désordonné rapidement.

Il serait peut-être plus prudent de regrouper le float et le char array. Insérez le membre flottant et extrayez les 4 octets (ou la longueur souhaitée).