1

Since my Numerical Analysis course exam is near, I was searching for a implementation code to to represent floating point numbers in C/C++? Then I found a line from one the codes in github. Can you please tell me, what is the meaning of the second line in the code snippet below, and how and why this is important?

float x = ...;
unsigned u = *(unsigned*)&x; 
fabian
  • 80,457
  • 12
  • 86
  • 114
  • Please read [ask]. What is `x` ? Almost all platforms use IEEE 754 to represent floating point numbers see https://en.wikipedia.org/wiki/Floating-point_arithmetic – Richard Critten Jun 15 '22 at 08:01
  • It's impossible to tell why one line of code is important when there is no context at all. The line of code could be completely unimportant for all anyone can tell. – john Jun 15 '22 at 08:03
  • 1
    Written with C++ casts this would be `unsigned u = *reinterpret_cast(&x);` i.e. the compiler is forced to ignore the fact that `&x` has type `float*` and interpret it as `unsigned*`. If you want to inspect the binary representation, I'd recommend an `reinterpret_cast` to `uint8_t*` to inspect the memory representation byte by byte. (Treat the result of the cast as if it would point to the first element of a array `uint8_t[sizeof(x)]`) – fabian Jun 15 '22 at 08:07
  • [Dereferencing the address of a pointer-casted variable](https://stackoverflow.com/q/56074903/995714), [Why cast to a pointer then dereference?](https://stackoverflow.com/q/39312058/995714) – phuclv Jun 15 '22 at 10:01
  • It interprets the memory occupied by `x` as if it contains an `unsigned` instead of a `float`. It also formally gives undefined behaviour. On particular platforms it might be meaningful. For example, if an `unsigned` is 32-bit and a `float` is 32-bit (that's not guaranteed with all compilers) `u` and `x` will often end up containing the same sequence of bits/bytes in memory - but that sequence of bits has different meaning if interpreted as an `unsigned` than if it is interpreted as a `float`. – Peter Jun 15 '22 at 10:36
  • That snippet looks like a bad (i.e., **undefined behavior**, via type punning and big hammer casting) way to do `static_assert(sizeof(float) == sizeof(unsigned)); float f = 123.0f; unsigned u; std::memcpy(&u, &f, sizeof u);` – Eljay Jun 15 '22 at 12:22
  • It says "let's pretend that the memory named `x` actually represents a value whose type is `unsigned int`'. Like all make-believe games, it may or may not correspond to reality. – Pete Becker Jun 15 '22 at 14:00

3 Answers3

6

unsigned is just short for unsigned int and using C++-style casts the line would translate to

unsigned int u = *reinterpret_cast<unsigned int*>(&x);

However read below why this causes undefined behavior in either case.

(I recommend to not use C-style casts as in the line shown in the question, since it is not obvious to which C++-style cast they resolve.)

If x is a float variable, then the line is trying to reinterpret the object representation of the float variable as the object representation of an unsigned int, basically to reinterpret the float's memory as the memory of an unsigned int, and then stores the unsigned int value corresponding to that representation in u.

Step for step, &x is a pointer to x of type float*, reinterpret_cast<unsigned int*>(&x) is a pointer to x, but now of type unsigned int*. And then *reinterpret_cast<unsigned int*>(&x) is supposed to dereference that unsigned int* pointer to the float variable to retrieve an unsigned int value from the pointed-to memory location as if the bytes stored there represented an unsigned int value instead of a float value. Finally unsigned int u = is supposed to use that value to initialize u with it.

That causes undefined behavior because it is an aliasing violation to access a float object through a unsigned int* pointer. Some compilers have options which can be enabled to allow this (under the assumption that float and unsigned int have compatible size and alignment), but it is not permitted by the standard C++ language itself.

Generally, whenever you see reinterpret_cast (or a C-style cast that might resolve to a reinterpret_cast), you are likely to cause undefined behavior if you don't know exactly what you are doing.


Since C++20 the correct way to do this without undefined behavior is using std::bit_cast:

float x = /*...*/;
auto u = std::bit_cast<unsigned>(x);

or before C++20 using std::memcpy:

float x = /*...*/;
unsigned u;
static_assert(sizeof(u) == sizeof(x));
std::memcpy(&u, &x, sizeof(u));

The size verification is done by std::bit_cast automatically. Even without C++20 it would probably be a good idea to wrap the static_assert and memcpy in a similar generic function for reuse.

Both of these still require that the representation of x is also a valid representation for a u. Otherwise the behavior is still undefined. I don't know whether there even is any C++ implementation where this doesn't hold for all values in the float -> unsigned case.


Also as an additional note: C is a different language. The rules may well be different in C. For example there is obviously no reinterpret_cast in C to which the (unsigned*) cast could resolve and the object model is very different. In this case though, C's aliasing rules will have an equivalent effect.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • Perhaps a note about *dereference that `unsigned int*` pointer to the float variable to retrieve its value as a `unsigned int`*: it's not the value of the float, but its bit representation taken as `unsigned int`. – j6t Jun 15 '22 at 08:43
  • @j6t Maybe its clearer now, I hope. – user17732522 Jun 15 '22 at 08:48
  • no, it's not "idiomatic" because it invokes UB: [Is reinterpret_cast type punning actually undefined behavior?](https://stackoverflow.com/q/53995657/995714), [Is this type punning well-defined?](https://stackoverflow.com/q/38000599/995714). The only valid way to type pun in C++ is via `std::bit_cast` – phuclv Jun 15 '22 at 09:54
  • @phuclv Yes, I explain later that this causes undefined behavior. I put a note at the beginning to make it clear. – user17732522 Jun 15 '22 at 09:59
  • 2
    user17732522, Another pitfall: `unsigned` and `float` are not certainly the same size. – chux - Reinstate Monica Jun 15 '22 at 12:51
  • @chux-ReinstateMonica "_(under the assumption that float and unsigned int have compatible size and alignment)_": That's why I mentioned it with regards to extensions such as `-fno-strict-aliasing`. – user17732522 Jun 15 '22 at 14:58
1

It is not valid C++. The behavior (of the program) is undefined.
The cast expression would cause the alignment requirement to be violated (aka "strict aliasing violation").
See: §6.7 Memory and objects, and §6.8 Types of ISO/IEC JTC1 SC22 WG21.

The problem is the explicit cast will become a reinterpret_cast:

float boat = 420.69f;
// unsigned dont_do_this = * reinterpret_cast<unsigned *> (&boat);
//          ~~~~~~~~~~~~~~~^
//  Dereferencing `unsigned*` pointer which doesn't point to an `unsigned`.

float* is not Pointer-Interconvertible with unsigned*. You could do this, instead:

auto since_cpp20 = std::bit_cast<unsigned>(boat); // include <bit>
// Alternatively:
unsigned since_c;
std::memcpy(&since_c, &boat, sizeof since_c);
viraltaco_
  • 814
  • 5
  • 14
-1

Assuming that x is float, then the code is a hacky way to access the binary format of a float by copying its bits directly to an integer, i.e. without doing the normal floating point to integer conversion that would happen if you just wrote u = x;

john
  • 85,011
  • 4
  • 57
  • 81