最有效的符合标准的方法,将int重新解释为float

yur*_*hek 31 c++ standards-compliance type-conversion language-lawyer c++11

假设我有floatIEEE 754 binary32的保证.给定一个与存储的有效浮点相对应的位模式std::uint32_t,如何float以最有效的标准兼容方式将其重新解释为?

float reinterpret_as_float(std::uint32_t ui) {
   return /* apply sorcery to ui */;
}
Run Code Online (Sandbox Code Playgroud)

我有几种方法,我知道/怀疑/假设有一些问题:

  1. 通过reinterpret_cast,

    float reinterpret_as_float(std::uint32_t ui) {
        return reinterpret_cast<float&>(ui);
    }
    
    Run Code Online (Sandbox Code Playgroud)

    或者等价的

    float reinterpret_as_float(std::uint32_t ui) {
        return *reinterpret_cast<float*>(&ui);
    }
    
    Run Code Online (Sandbox Code Playgroud)

    哪个遭受别名问题.

  2. 通过union,

    float reinterpret_as_float(std::uint32_t ui) {
        union {
            std::uint32_t ui;
            float f;
        } u = {ui};
        return u.f;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    这实际上并不合法,因为它只允许从最近写的成员读取.然而,似乎有些编译器(gcc)允许这样做.

  3. 通过std::memcpy,

    float reinterpret_as_float(std::uint32_t ui) {
        float f;
        std::memcpy(&f, &ui, 4);
        return f;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    哪种AFAIK是合法的,但复制单个单词的函数调用似乎很浪费,尽管它可能会被优化掉.

  4. 通过reinterpret_cast荷兰国际集团到char*和复制,

    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;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    哪个AFAIK也合法,因为char指针可以免除别名问题,手动字节复制循环可以保存可能的函数调用.循环肯定会展开,但4个可能单独的单字节加载/存储是令人担忧的,我不知道这是否可以优化到单个四字节加载/存储.

4是我能想到的最好的.

到目前为止我是否正确?有没有更好的方法来做到这一点,特别是保证单一加载/存储?

cma*_*ter 14

Afaik,只有两种方法符合严格的别名规则:memcpy()并且char*通过复制进行转换.所有其他人从属于a float的内存中读取a uint32_t,并且允许编译器在写入该内存位置之前执行读取.它甚至可以完全优化写入,因为它可以证明根据严格的别名规则永远不会使用存储的值,从而产生垃圾返回值.

这真的取决于编译/优化是否memcpy()char*复制速度更快.在这两种情况下,智能编译器可能能够确定它只能加载和复制uint32_t,但我不相信任何编译器在我在生成的汇编代码中看到它之前这样做.

编辑:
在使用gcc 4.8.1进行一些测试之后,我可以说这种memcpy()方法对于这个特殊的编译器是最好的,详见下文.


编译

#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;
}
Run Code Online (Sandbox Code Playgroud)

使用gcc -S -std=gnu11 -O3 foo.c这个汇编代码:

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
Run Code Online (Sandbox Code Playgroud)

这不是最佳选择.

做同样的事情

#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;
}
Run Code Online (Sandbox Code Playgroud)

收益率(除了所有优化级别-O0):

movl    %edi, -4(%rsp)
movss   -4(%rsp), %xmm0
ret
Run Code Online (Sandbox Code Playgroud)

这是最佳的.