繁体   English   中英

如何在x86-64上获取80位长双精度数的尾数作为int

[英]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.hsizeof(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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM