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 one reinterpret it as a float
in a most efficient standard compliant way?
float reinterpret_as_float(std::uint32_t ui) {
return /* apply sorcery to ui */;
}
I've got a few ways that I know/suspect/assume have some issues:
Via
reinterpret_cast
,float reinterpret_as_float(std::uint32_t ui) { return reinterpret_cast<float&>(ui); }
or equivalently
float reinterpret_as_float(std::uint32_t ui) { return *reinterpret_cast<float*>(&ui); }
which suffers from aliasing issues.
Via
union
,float reinterpret_as_float(std::uint32_t ui) { union { std::uint32_t ui; float f; } u = {ui}; return u.f; }
which is not actually legal, as it is only allowed to read from most recently written to member. Yet, it seems some compilers (gcc) allow this.
Via
std::memcpy
,float reinterpret_as_float(std::uint32_t ui) { float f; std::memcpy(&f, &ui, 4); return f; }
which AFAIK is legal, but a function call to copy single word seems wasteful, though it might get optimized away.
Via
reinterpret_cast
ing tochar*
and copying,float reinterpret_as_float(std::uint32_t ui) { char* uip = reinterpret_cast<char*>(&ui); float f; char* fp = reinterpret_cast<char*>(&f); for (int i = 0; i < 4; ++i) { fp[i] = uip[i]; } return f; }
which AFAIK is also legal, as
char
pointers are exempt from aliasing issues and manual byte copying loop saves a possible function call. The loop will most definitely be unrolled, yet 4 possibly separate one-byte loads/stores are worrisome, I have no idea whether this is optimizable to single four byte load/store.
The 4
is the best I've been able to come up with.
Am I correct so far? Is there a better way to do this, particulary one that will guarantee single load/store?
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.
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).
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?
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
来源:https://stackoverflow.com/questions/20762952/most-efficient-standard-compliant-way-of-reinterpreting-int-as-float