11

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.

phuclv
  • 37,963
  • 15
  • 156
  • 475
Stewart
  • 4,356
  • 2
  • 27
  • 59
  • 1
    If you are compiling as C++, a C solution need not work. Different languages and all that. Or are you gonna compile that one function by a C compiler and link it in? – StoryTeller - Unslander Monica Feb 15 '18 at 09:05
  • If a C-developer has a C-solution, I'd be happy to accept it because it'd probably work in C++ as well. This is more a question of syntax than anything. – Stewart Feb 15 '18 at 09:08
  • There's no way to do this without 'breaking the rules' or writing unsafe code. The union approach seems best to me. – john Feb 15 '18 at 09:09
  • 2
    Rubbish. Punning via union **is** the C solution. It's not undefined behavior, and will do exactly what you want so long as you don't end up with a trap representation. And yet in C++ it's undefined behavior. See the problem with double tagging? – StoryTeller - Unslander Monica Feb 15 '18 at 09:10
  • What should happen when `sizeof(float) != sizeof(std::uint32_t)`? – Zereges Feb 15 '18 at 09:16
  • `float ieee_float(uint32_t f) { void *p = &f; float fv = *(float*)p; return fv; }` but the `union` is cleaner `typedef union { float f; uint32_t v; } fu;` then `float ieee_float(uint32_t f) { fu.v = f; return fu.f; }` – David C. Rankin Feb 15 '18 at 09:20

3 Answers3

13

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

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
2

There's no C/C++ language. They're different languages with different rules. The valid way in C is to use a union, but that's not allowed in C++. See

In older C++ standards you have to use std::memcpy. Even reinterpret_cast for type punning invokes undefined behavior, hence disallowed. In C++20 a new cast type called std::bit_cast was created exactly for this purpose

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

See also:

phuclv
  • 37,963
  • 15
  • 156
  • 475
  • Some casts are allowed according to type casting rules. – Sebastian Jul 06 '22 at 13:35
  • @Sebastian which cast are you talking about? Nothing other than `bit_cast` is allowed to do type punning – phuclv Jul 13 '22 at 01:16
  • See type aliasing section: https://en.cppreference.com/w/cpp/language/reinterpret_cast Cast to `std::byte`, `char` and `unsigned char` (but not `signed char`, if not the same as `char` depending on platform) is allowed. – Sebastian Jul 13 '22 at 05:21
0

You have several options, as stated here:

  • Use the union solution: since C11 it is explicitly allowed (as said in the other answer).
  • Instead of using an array of 32 bit words, use an array of 8 bit words (uint8_t), since char types can be aliased to any type.
LoPiTaL
  • 2,495
  • 16
  • 23