[英]How to get the mantissa of an 80-bit long double as an int on x86-64
frexpl
将不起作用,因为它将尾数保留为长双frexpl
一部分。 我可以使用punning类型,还是危险? 还有另一种方法吗?
x86的浮点数和整数字节序为little-endian,因此有效位数(即尾数)为80位x87 long double
的低64位。
在汇编中,您只需按正常方式加载,例如mov rax, [rdi]
。
与IEEE binary32( float
)或binary64( double
)不同,80位长的double显式地将前1存储在有效位中。 (对于次标准为0
)。 https://en.wikipedia.org/wiki/Extended_precision#x86_extended_precision_format
因此,有效有效位数的无符号整数值(幅度)与对象表示中实际存储的值相同。
如果要签名int
,那就太糟糕了; 包括符号位在内,它将是65位,但是在任何x86 C实现中, int
仅为32位。
如果要使用int64_t
,则可以右移1以丢弃低位,从而为符号位腾出空间。 然后,如果设置了符号位,则进行2的补码求反,使有效值的符号2的补码表示除以2。(IEEE FP使用符号/幅度,在位模式的顶部带有符号位)
在C / C ++中,是的,您需要打双关,例如,使用union或memcpy
。 x86 / x86-64上所有暴露80位浮点数的所有C实现都使用12或16字节类型,底部10字节值。
请注意,MSVC使用long double
= double
(64位浮点数),因此请从float.h
或sizeof(long double)
检查LDBL_MANT_DIG
。 所有3条static_assert()
语句均在MSVC上触发,因此它们都完成了自己的工作,并使我们免于将整个binary64 double
(sign / exp / mantissa)复制到我们的uint64_t
。
// valid C11 and C++11
#include <float.h> // float numeric-limit macros
#include <stdint.h>
#include <assert.h> // C11 static assert
#include <string.h> // memcpy
// inline
uint64_t ldbl_mant(long double x)
{
// we can assume CHAR_BIT = 8 when targeting x86, unless you care about DeathStation 9000 implementations.
static_assert( sizeof(long double) >= 10, "x87 long double must be >= 10 bytes" );
static_assert( LDBL_MANT_DIG == 64, "x87 long double significand must be 64 bits" );
uint64_t retval;
memcpy(&retval, &x, sizeof(retval));
static_assert( sizeof(retval) < sizeof(x), "uint64_t should be strictly smaller than long double" ); // sanity check for wrong types
return retval;
}
它可以作为独立函数在gcc / clang / ICC (在Godbolt上)上高效地编译为一条指令(因为调用约定将long double
的内存传递)。 在x87寄存器中以long double
精度插入代码后,可能会编译为TBYTE x87存储并进行整数重载。
## gcc/clang/ICC -O3 for x86-64
ldbl_mant:
mov rax, QWORD PTR [rsp+8]
ret
对于32位,gcc有一个奇怪的冗余副本错过优化错误,而ICC和clang则没有。 他们只是从函数arg进行2次加载,而不先复制。
# GCC -m32 -O3 copies for no reason
ldbl_mant:
sub esp, 28
fld TBYTE PTR [esp+32] # load the stack arg
fstp TBYTE PTR [esp] # store a local
mov eax, DWORD PTR [esp]
mov edx, DWORD PTR [esp+4] # return uint64_t in edx:eax
add esp, 28
ret
C99使联合类型操作严格定义为行为,GNU C ++也是如此。 我认为MSVC也定义了它。
但是memcpy
始终是可移植的,因此这可能是一个更好的选择,在这种情况下,我们只需要一个元素,就更容易阅读。
如果还需要指数和符号位,则结构与long double
精度数之间的并集可能会很好,除了在结构末尾对齐时使用的填充会使其变大。 不过,不太可能在uint64_t
成员之后的uint16_t
成员之后进行填充。 但是我会担心:1
和:15
位域,因为IIRC是实现定义的,位域的成员以什么顺序存储。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.