Search code examples
c++castingfloating-pointtype-punning

bitwise casting uint32_t to float in C/C++


I'm receiving a buffer from a network which was converted to an array of 32-bit words. I have one word which is defined as an IEEE-754 float by my interface document. I need to extract this word from the buffer. It's tough to cast from one type to another without invoking a conversion. The bits are already adhere to the IEEE-754 float standard, I don't want to re-arrange any bits.

My first try was to cast the address of the uint32_t to a void*, then convert the void* to a float*, then dereference as a float:

float ieee_float(uint32_t f)
{
    return *((float*)((void*)(&f)));
}

error: dereferencing type-punned pointer will break strict-aliasing rules [-Werror=strict-aliasing]

My second try was like this:

float ieee_float(uint32_t f)
{
    union int_float{
        uint32_t i;
        float f;
    } tofloat;

    tofloat.i = f;
    return tofloat.f;
}

However, word on the street is that unions are totally unsafe. It's undefined behavior to read from the member of the union that wasn't most recently written.

So I tried a more C++ approach:

float ieee_float(uint32_t f)
{
  return *reinterpret_cast<float*>(&f);
}

error: dereferencing type-punned pointer will break strict-aliasing rules [-Werror=strict-aliasing]

My next thought was "screw it. Why am I dealing with pointers anyways?" and just tried:

float ieee_float(uint32_t f)
{
  return reinterpret_cast<float>(f);
}

error: invalid cast from type ‘uint32_t {aka unsigned int}’ to type ‘float’

Is there a way to do the conversion without triggering the warning/error? I'm compiling with g++ using -Wall -Werror. I'd prefer to not touch compiler settings.

I tagged C because a c-solution is acceptable.


Solution

  • In C++20, you can use std::bit_cast:

    float ieee_float(uint32_t f)
    {
        return std::bit_cast<float>(f);
    }
    

    In C++17 and before, the right way™ is:

    float ieee_float(uint32_t f)
    {
        static_assert(sizeof(float) == sizeof f, "`float` has a weird size.");
        float ret;
        std::memcpy(&ret, &f, sizeof(float));
        return ret;
    }
    

    Both GCC and Clang at -O1 and above generate the same assembly for this code and a naive reinterpret_cast<float &>(f) (but the latter is undefined behavior, and might not work in some scenarios).