c++ - Construyendo un flotador de 32 bits a partir de sus 4 bytes compuestos
floating-point endianness (5)
Las siguientes funciones empaquetan / desempaquetan bytes que representan un valor de punto flotante de precisión simple a / desde un búfer en orden de bytes de red. Solo el método de paquete debe tener en cuenta la endianidad, ya que el método de desempaquetar construye explícitamente el valor de 32 bits a partir de los bytes individuales, desplazándolos en la cantidad apropiada y luego ORDENándolos juntos. Estas funciones solo son válidas para implementaciones de C / C ++ que almacenan un flotador en 32 bits. Esto es cierto para las implementaciones de punto flotante IEEE 754-1985 .
// 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;
}
Estoy tratando de construir un flotador de 32 bits a partir de sus 4 bytes compuestos. ¿Hay una forma mejor (o más portátil) de hacerlo que con el siguiente método?
#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;
}
No hay manera de hacer esto portátil, ya que diferentes plataformas pueden usar:
- Ordenamiento de bytes diferentes (big endian vs. little endian)
- diferentes representaciones para valores de punto flotante (ver http://en.wikipedia.org/wiki/IEEE_754-1985 para un ejemplo)
- Diferentes tamaños para valores de punto flotante.
También me pregunto de dónde sacas estos 4 bytes?
Si asumo que los obtiene de otro sistema y puede garantizar que ambos sistemas usan exactamente el mismo método para almacenar valores de punto flotante en la memoria, puede usar el truco de unión. De lo contrario, es casi seguro que su código no sea portátil.
Podrías usar un memcpy
( Result )
float f;
uchar b[] = {b3, b2, b1, b0};
memcpy(&f, &b, sizeof(f));
return f;
o una unión * ( Result )
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 su código, ya que no hay garantía de que la plataforma sea un poco endian o que el float
esté utilizando el binario IEEE32 o incluso sizeof(float) == 4
.
(Nota *: Como lo explica @James , técnicamente no está permitido en el estándar (C ++ § [class.union] / 1) para acceder al miembro de la unión uf
.)
Puedes usar 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 corte de la unión, lo que técnicamente no está permitido por el idioma. También evita el reinterpret_cast<float*>(byte_array)
que se usa comúnmente, lo que infringe las reglas estrictas de alias (está permitido reinterpretar cualquier objeto como una matriz de caracteres, por lo que los reinterpret_cast
en esta solución no violan las reglas estrictas de alias) .
Todavía se basa en que el float
cuatro bytes de ancho y se basa en que sus cuatro bytes son un número de punto flotante válido en el formato de punto flotante de su implementación, pero debe hacer esas suposiciones o escribir un código de manejo especial para realizar la conversión.
Si desea una forma portátil de hacer esto, 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;
}