将int重新解释为float的最有效的标准兼容方式



假设我保证float是IEEE 754二进制32。给定一个对应于存储在std::uint32_t中的有效浮点的位模式,如何以最有效的符合标准的方式将其重新解释为float

float reinterpret_as_float(std::uint32_t ui) {
return /* apply sorcery to ui */;
}

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

  1. 通过reinterpret_cast

    float reinterpret_as_float(std::uint32_t ui) {
    return reinterpret_cast<float&>(ui);
    }
    

    或等效

    float reinterpret_as_float(std::uint32_t ui) {
    return *reinterpret_cast<float*>(&ui);
    }
    

    其存在混叠问题。

  2. 通过union

    float reinterpret_as_float(std::uint32_t ui) {
    union {
    std::uint32_t ui;
    float f;
    } u = {ui};
    return u.f;
    }
    

    这实际上是不合法的,因为它只允许阅读最近写给会员的信。然而,似乎有些编译器(gcc)允许这样做。

  3. 通过std::memcpy

    float reinterpret_as_float(std::uint32_t ui) {
    float f;
    std::memcpy(&f, &ui, 4);
    return f;
    }
    

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

  4. 通过reinterpret_castchar*并复制,

    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;
    }
    

    AFAIK也是合法的,因为char指针不受混叠问题的影响,并且手动字节复制循环保存了可能的函数调用。循环肯定会被展开,但4个可能单独的一字节加载/存储令人担忧,我不知道这是否可以优化为单个四字节加载/保存。

4是我能想到的最好的。

到目前为止我是对的吗?有没有更好的方法可以做到这一点,特别是保证单一加载/存储的方法?

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

这实际上取决于编译器/优化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;
}

使用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

这不是最优的。

对做同样的操作

#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;
}

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

movl    %edi, -4(%rsp)
movss   -4(%rsp), %xmm0
ret

这是最优的。

如果整数变量中的位模式与有效的float值相同,则并集可能是最好、最符合要求的方法。如果你阅读了说明书,这实际上是合法的(现在不记得那一节了)。

memcpy始终是安全的,但确实涉及复制

铸造可能导致问题

并集-似乎在C99和C11中是允许的,不确定C++

看看:

什么是严格的混叠规则?

C99中是否未指定通过并集的类型punning,C11中是否已指定?

float reinterpret_as_float(std::uint32_t ui) {
return *((float *)&ui);
}

作为普通函数,它的代码被翻译成汇编如下(Pelles C for Windows):

fld [esp+4]
ret

如果定义为inline函数,那么类似这样的代码(n为无符号,x为浮点):

x = reinterpret_as_float (n);

翻译成汇编程序如下:

fld [ebp-4]  ;RHS of asignment. Read n as float
fstp dword ptr [ebp-8]  ;LHS of asignment

相关内容

最新更新