Assume I have guarantees that float
is IEEE 754 binary32. Given a bit pattern that corresponds to a valid float, stored in std::uint32_t
, how does
Afaik, there are only two approaches that are compliant with strict aliasing rules: memcpy()
and cast to char*
with copying. All others read a float
from memory that belongs to an uint32_t
, and the compiler is allowed to perform the read before the write to that memory location. It might even optimize away the write altogether as it can prove that the stored value will never be used according to strict aliasing rules, resulting in a garbage return value.
It really depends on the compiler/optimizes whether memcpy()
or char*
copy is faster. In both cases, an intelligent compiler might be able to figure out that it can just load and copy an uint32_t
, but I would not trust any compiler to do so before I have seen it in the resulting assembler code.
Edit:
After some testing with gcc 4.8.1, I can say that the memcpy()
approach is the best for this particulare compiler, see below for details.
Compiling
#include <stdint.h>
float foo(uint32_t a) {
float b;
char* aPointer = (char*)&a, *bPointer = (char*)&b;
for( int i = sizeof(a); i--; ) bPointer[i] = aPointer[i];
return b;
}
with gcc -S -std=gnu11 -O3 foo.c
yields this assemble code:
movl %edi, %ecx
movl %edi, %edx
movl %edi, %eax
shrl $24, %ecx
shrl $16, %edx
shrw $8, %ax
movb %cl, -1(%rsp)
movb %dl, -2(%rsp)
movb %al, -3(%rsp)
movb %dil, -4(%rsp)
movss -4(%rsp), %xmm0
ret
This is not optimal.
Doing the same with
#include <stdint.h>
#include <string.h>
float foo(uint32_t a) {
float b;
char* aPointer = (char*)&a, *bPointer = (char*)&b;
memcpy(bPointer, aPointer, sizeof(a));
return b;
}
yields (with all optimization levels except -O0
):
movl %edi, -4(%rsp)
movss -4(%rsp), %xmm0
ret
This is optimal.
float reinterpret_as_float(std::uint32_t ui) {
return *((float *)&ui);
}
As plain function, its code is translated into assembly as this (Pelles C for Windows):
fld [esp+4]
ret
If defined as inline
function, then a code like this (n
being unsigned, x
being float):
x = reinterpret_as_float (n);
Is translated to assembler as this:
fld [ebp-4] ;RHS of asignment. Read n as float
fstp dword ptr [ebp-8] ;LHS of asignment
memcpy is always safe but does involve a copy
casting may lead to problems
union - seems to be allowed in C99 and C11, not sure about C++
Take a look at:
What is the strict aliasing rule?
and
Is type-punning through a union unspecified in C99, and has it become specified in C11?
If the bitpattern in the integer variable is the same as a valid float
value, then union is probably the best and most compliant way to go. And it's actually legal if you read the specification (don't remember the section at the moment).