web-dev-qa-db-fra.com

Construire un flottant 32 bits à partir de ses 4 octets composites

J'essaie de construire un flotteur 32 bits à partir de ses 4 octets composites. Existe-t-il une meilleure façon (ou plus portable) de le faire qu'avec la méthode suivante?

#include <iostream>

typedef unsigned char uchar;

float bytesToFloat(uchar b0, uchar b1, uchar b2, uchar b3)
{
    float output;

    *((uchar*)(&output) + 3) = b0;
    *((uchar*)(&output) + 2) = b1;
    *((uchar*)(&output) + 1) = b2;
    *((uchar*)(&output) + 0) = b3;

    return output;
}

int main()
{
    std::cout << bytesToFloat(0x3e, 0xaa, 0xaa, 0xab) << std::endl; // 1.0 / 3.0
    std::cout << bytesToFloat(0x7f, 0x7f, 0xff, 0xff) << std::endl; // 3.4028234 × 10^38  (max single precision)

    return 0;
}
25
Madgeek

Vous pouvez utiliser un memcpy ( Résultat )

float f;
uchar b[] = {b3, b2, b1, b0};
memcpy(&f, &b, sizeof(f));
return f;

ou une union * ( Résultat )

union {
  float f;
  uchar b[4];
} u;
u.b[3] = b0;
u.b[2] = b1;
u.b[1] = b2;
u.b[0] = b3;
return u.f;

Mais ce n'est pas plus portable que votre code, car il n'y a aucune garantie que la plate-forme est peu endienne ou que le float utilise IEEE binary32 ou même sizeof(float) == 4.

(Remarque *: comme expliqué par @ James , il n'est techniquement pas autorisé dans la norme (C++ § [class.union]/1) d'accéder au membre de l'union u.f.)

34
kennytm

Les fonctions suivantes regroupent/décompressent les octets représentant une valeur à virgule flottante simple précision vers/depuis un tampon dans l'ordre des octets du réseau. Seule la méthode pack doit prendre en compte l'endianité, car la méthode unpack construit explicitement la valeur 32 bits à partir des octets individuels en les décalant par bits de la quantité appropriée, puis en les OU ensemble. Ces fonctions ne sont valables que pour les implémentations C/C++ qui stockent un flottant en 32 bits. Cela est vrai pour IEEE 754-1985 implémentations en virgule flottante.

// unpack method for retrieving data in network byte,
//   big endian, order (MSB first)
// increments index i by the number of bytes unpacked
// usage:
//   int i = 0;
//   float x = unpackFloat(&buffer[i], &i);
//   float y = unpackFloat(&buffer[i], &i);
//   float z = unpackFloat(&buffer[i], &i);
float unpackFloat(const void *buf, int *i) {
    const unsigned char *b = (const unsigned char *)buf;
    uint32_t temp = 0;
    *i += 4;
    temp = ((b[0] << 24) |
            (b[1] << 16) |
            (b[2] <<  8) |
             b[3]);
    return *((float *) &temp);
}

// pack method for storing data in network,
//   big endian, byte order (MSB first)
// returns number of bytes packed
// usage:
//   float x, y, z;
//   int i = 0;
//   i += packFloat(&buffer[i], x);
//   i += packFloat(&buffer[i], y);
//   i += packFloat(&buffer[i], z);
int packFloat(void *buf, float x) {
    unsigned char *b = (unsigned char *)buf;
    unsigned char *p = (unsigned char *) &x;
#if defined (_M_IX86) || (defined (CPU_FAMILY) && (CPU_FAMILY == I80X86))
    b[0] = p[3];
    b[1] = p[2];
    b[2] = p[1];
    b[3] = p[0];
#else
    b[0] = p[0];
    b[1] = p[1];
    b[2] = p[2];
    b[3] = p[3];
#endif
    return 4;
}
16
jholl

Vous pouvez utiliser std::copy:

float bytesToFloat(uchar b0, uchar b1, uchar b2, uchar b3) 
{ 
    uchar byte_array[] = { b3, b2, b1, b0 };
    float result;
    std::copy(reinterpret_cast<const char*>(&byte_array[0]),
              reinterpret_cast<const char*>(&byte_array[4]),
              reinterpret_cast<char*>(&result));
    return result;
} 

Cela évite le piratage d'union, qui n'est pas techniquement autorisé par la langue. Il évite également la reinterpret_cast<float*>(byte_array) couramment utilisée, qui viole les règles strictes d'alias (il est autorisé de réinterpréter tout objet sous la forme d'un tableau de char, donc les reinterpret_cast S dans ce ne violent pas les règles strictes d'alias).

Il repose toujours sur float sur quatre octets de largeur et repose sur le fait que vos quatre octets sont un nombre à virgule flottante valide dans le format à virgule flottante de votre implémentation, mais vous devez soit faire ces hypothèses, soit écrire un code de traitement spécial pour faire la conversion.

14
James McNellis

Il n'y a aucun moyen de le faire portable, car différentes plates-formes peuvent utiliser:

  • ordre différent des octets (gros-boutien vs petit-boutien)
  • différentes représentations pour les valeurs à virgule flottante (voir http://en.wikipedia.org/wiki/IEEE_754-1985 pour un exemple)
  • différentes tailles pour les valeurs à virgule flottante

Je me demande aussi d'où vous obtenez ces 4 octets?

Si je suppose que vous les obtenez d'un autre système et que vous pouvez garantir que les deux systèmes utilisent exactement la même méthode pour stocker des valeurs à virgule flottante en mémoire, vous pouvez utiliser l'astuce d'union. Sinon, votre code est presque garanti non portable.

4
Patrick

Si vous voulez un moyen portable de le faire, vous devrez écrire un peu de code pour détecter l'endianisme du système.

float bytesToFloatA(uchar b0, uchar b1, uchar b2, uchar b3)
{
    float output;

    *((uchar*)(&output) + 3) = b0;
    *((uchar*)(&output) + 2) = b1;
    *((uchar*)(&output) + 1) = b2;
    *((uchar*)(&output) + 0) = b3;

    return output;
}


float bytesToFloatB(uchar b0, uchar b1, uchar b2, uchar b3)
{
    float output;

    *((uchar*)(&output) + 3) = b3;
    *((uchar*)(&output) + 2) = b2;
    *((uchar*)(&output) + 1) = b1;
    *((uchar*)(&output) + 0) = b0;

    return output;
}

float (*correctFunction)(uchar b0, uchar b1, uchar b2, uchar b3) = bytesToFloatA;

if ((*correctFunction)(0x3e, 0xaa, 0xaa, 0xab) != 1.f/3.f) // horrifying, I know
{
  correctFunction = bytesToFloatB;
}
3
JoshD