假设我保证float
是IEEE 754二进制32。给定一个对应于存储在std::uint32_t
中的有效浮点的位模式,如何以最有效的符合标准的方式将其重新解释为float
?
float reinterpret_as_float(std::uint32_t ui) {
return /* apply sorcery to ui */;
}
我有几种方法知道/怀疑/假设存在一些问题:
通过
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); }
其存在混叠问题。
通过
union
、float reinterpret_as_float(std::uint32_t ui) { union { std::uint32_t ui; float f; } u = {ui}; return u.f; }
这实际上是不合法的,因为它只允许阅读最近写给会员的信。然而,似乎有些编译器(gcc)允许这样做。
通过
std::memcpy
、float reinterpret_as_float(std::uint32_t ui) { float f; std::memcpy(&f, &ui, 4); return f; }
AFAIK是合法的,但复制单个单词的函数调用似乎是浪费的,尽管它可能会被优化掉。
通过
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; }
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