Pregunta

Estoy tratando de construir un flotador de 32 bits a partir de sus compuestos 4 bytes. ¿Hay una manera mejor (o más portable) para hacer esto que con el método siguiente?

#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;
}
¿Fue útil?

Solución

Se puede usar un memcpy ( Resultado )

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

o una unión * ( Resultado )

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;

Pero esto no es más portátil que el código, ya que no hay garantía de que la plataforma es ascendente hacia la izquierda o la float está utilizando IEEE binary32 o incluso sizeof(float) == 4.

(* Nota: Como se explica por @ James , técnicamente no es permitido en la norma (C ++ § [class.union] / 1) para acceder a la u.f miembro de un sindicato.)

Otros consejos

Las siguientes funciones del paquete / bytes de desempaquetado que representan una precisión simple valor de punto flotante a / de un tampón de orden de bytes de red. Sólo el método paquete necesita tomar endianness en cuenta ya que el método de desempaquetado construye explícitamente el valor de 32 bits de los bytes individuales por bit ellos el desplazamiento de la cantidad apropiada y luego OR de todas ellas. Estas funciones sólo son válidas para las implementaciones de C / C ++ que almacenan un flotador en 32 bits. Esto es cierto para IEEE 754-1985 implementaciones de coma flotante.

// 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;
}

Se puede utilizar 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;
} 

Esto evita el hack unión, que no está permitido por el lenguaje técnico. También evita la reinterpret_cast<float*>(byte_array) comúnmente usado, lo cual viola las estrictas normas de aliasing (se permite reinterpretar cualquier objeto como una matriz de char, por lo que los reinterpret_casts en esta solución no violan las estrictas normas de aliasing).

Todavía se basa en float siendo cuatro bytes de ancho y se basa en sus cuatro bytes son un número de punto flotante válido en formato de coma flotante de su aplicación, pero que o bien tienen que hacer esos supuestos o tiene que escribir el código de manejo especial para hacer la conversión.

No hay manera de hacer esto portátil, ya que las diferentes plataformas pueden utilizar:

  • diferente orden de bytes (big endian frente a little endian)
  • diferentes representaciones para los valores de punto flotante (ver http://en.wikipedia.org/wiki/ IEEE_754-1985 para un ejemplo)
  • diferentes tamaños para valores de coma flotante

También pregunto dónde obtener estos 4 bytes de?

Si asumo que las reciba de otro sistema, y ??se puede garantizar que ambos sistemas usan exactamente el mismo método para almacenar valores de coma flotante en la memoria, puede usar el truco unión. De lo contrario, su código es casi seguro que no portátil.

Si desea una forma portátil para hacer esto, usted tendrá que escribir un poco de código para detectar la endianess del sistema.

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;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top