33

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?

#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;
}
phuclv
  • 27,258
  • 11
  • 104
  • 360
Madgeek
  • 333
  • 1
  • 3
  • 6
  • 5
    Considering that this was my first question on Stack Overflow, I'm thrilled at the various responses. I thank everyone for their input. – Madgeek Oct 21 '10 at 23:47

6 Answers6

44

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

Community
  • 1
  • 1
kennytm
  • 469,458
  • 94
  • 1,022
  • 977
18

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
  • 1,994
  • 2
  • 17
  • 22
16

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
  • 327,682
  • 71
  • 882
  • 954
  • @JoshD: No; it still relies on `sizeof(float) == 4` and doesn't take endianness into consideration. It just avoids `reinterpret_cast(some_uchar_array)` and the union hack. – James McNellis Oct 21 '10 at 20:30
  • I'm fairly certain that `reinterpret_cast(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 Oct 22 '10 at 10:55
  • @MSalters: But `memcpy` doesn't reinterpret a byte array as a float; it reinterprets a float as a byte array. – James McNellis Oct 22 '10 at 15:36
  • 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 Oct 25 '10 at 08:17
4

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
  • 22,097
  • 9
  • 57
  • 125
3

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
  • 11,642
  • 2
  • 39
  • 52
  • 1
    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 Oct 21 '10 at 20:39
0

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;
}
Luke Hutchison
  • 6,034
  • 2
  • 30
  • 26