I\'m trying to build a 32-bit float out of its 4 composite bytes. Is there a better (or more portable) way to do this than with the following method?
#includ
I typically use this in C -- no memcpy
or union
required. It may break aliasing rules in C++, I don't know.
float bytesToFloat(uint8_t *bytes, bool big_endian) {
float f;
uint8_t *f_ptr = (uint8_t *) &f;
if (big_endian) {
f_ptr[3] = bytes[0];
f_ptr[2] = bytes[1];
f_ptr[1] = bytes[2];
f_ptr[0] = bytes[3];
} else {
f_ptr[3] = bytes[3];
f_ptr[2] = bytes[2];
f_ptr[1] = bytes[1];
f_ptr[0] = bytes[0];
}
return f;
}
If you have a whole array of bytes that need to be re-interpreted as floats, you can call the following procedure for each consecutive sequence of 4 bytes in the array if necessary, to switch the byte order (e.g. if you are running on a little endian machine, but the bytes are in big endian order). Then you can simply cast the uint8_t *
array pointer to float *
, and access the memory as an array of floats.
void switchEndianness(uint8_t *bytes) {
uint8_t b0 = bytes[0];
uint8_t b1 = bytes[1];
uint8_t b2 = bytes[2];
uint8_t b3 = bytes[3];
bytes[0] = b3;
bytes[1] = b2;
bytes[2] = b1;
bytes[3] = b0;
}
You could use a memcpy
(Result)
float f;
uchar b[] = {b3, b2, b1, b0};
memcpy(&f, &b, sizeof(f));
return f;
or a union* (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;
But this is no more portable than your code, since there is no guarantee that the platform is little-endian or the float
is using IEEE binary32 or even sizeof(float) == 4
.
(Note*: As explained by @James, it is technically not allowed in the standard (C++ §[class.union]/1) to access the union member u.f
.)
You can use 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;
}
This avoids the union hack, which isn't technically allowed by the language. It also avoids the commonly used reinterpret_cast<float*>(byte_array)
, which violates the strict aliasing rules (it is permitted to reinterpret any object as an array of char
, so the reinterpret_cast
s in this solution do not violate the strict aliasing rules).
It still relies on float
being four bytes in width and relies on your four bytes being a valid floating point number in your implementation's floating point format, but you either have to make those assumptions or you have to write special handling code to do the conversion.
The following functions pack/unpack bytes representing a single precision floating point value to/from a buffer in network byte order. Only the pack method needs to take endianness into account since the unpack method explicitly constructs the 32-bit value from the individual bytes by bit shifting them the appropriate amount and then OR-ing them together. These functions are only valid for C/C++ implementations that store a float in 32-bits. This is true for IEEE 754-1985 floating point implementations.
// 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;
}
There's no way to do this portable, since different platforms can use:
I also wonder where you get these 4 bytes from?
If I assume that you get them from another system, and you can guarantee that both systems use exactly the same method to store floating-point values in memory, you can use the union trick. Otherwise, your code is almost guaranteed to be non-portable.
If you want a portable way to do this, you'll have to write a bit of code to detect the endianess of the system.
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;
}