Most efficient standard-compliant way of reinterpreting int as float

只谈情不闲聊 提交于 2019-11-28 18:38:13

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

doron

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!