views:

230

answers:

5

I'm trying to build a 32bit float out of its 4 composite bytes. Is there a better (or more portable) way to do this than with the following method?

#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;
}
+11  A: 

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.)

KennyTM
To solve the `sizeof(float)` problem you may just declare the `b` member as `uchar b[sizeof(float)];`.
Matteo Italia
@Matteo: Right, but then the input needs to be modified as well.
KennyTM
@KennyTM: yes, that was implied. :)
Matteo Italia
Technically leads to UB, though.
GMan
+7  A: 

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_casts 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.

James McNellis
This still isn't portable, right? I'm just curious...
JoshD
@JoshD: No; it still relies on `sizeof(float) == 4` and doesn't take endianness into consideration. It just avoids `reinterpret_cast<float*>(some_uchar_array)` and the union hack.
James McNellis
I'm fairly certain that `reinterpret_cast<float*>(byte_array)` must be allowed if the byte_array (1) is aligned properly and (2) actually contains a float. I think so because otherwise it would be impossible to `memcpy` a `float` to another `float` (since `memcpy` writes to a byte array), and yet a `float` is the archetypical POD type.
MSalters
@MSalters: But `memcpy` doesn't reinterpret a byte array as a float; it reinterprets a float as a byte array.
James McNellis
@James McNellis: It's indeed not `memcpy` itself; that obviously works only on byte arrays. It's the guarantee that you can use the output byte array as a float.
MSalters
+1  A: 

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;
}
JoshD
That won't be equal in any endians because `1./3.` is a `double`, not a `float`. You should use something like `1.0f/3`.
KennyTM
+2  A: 

There's no way to do this portable, since different platforms can use:

  • different byte ordering (big endian vs. little endian)
  • different representations for floating point values (see http://en.wikipedia.org/wiki/IEEE_754-1985 for an example)
  • different sizes for floating point values

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.

Patrick
+4  A: 

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