gpt4 book ai didi

c - 如何在 x86-64 上将 80 位 long double 的尾数作为 int 获取

转载 作者:太空宇宙 更新时间:2023-11-04 06:46:21 27 4
gpt4 key购买 nike

frexpl 将不起作用,因为它将尾数保留为 long double 的一部分。我可以使用类型双关吗,否则会有危险吗?还有别的办法吗?

最佳答案

x86 的 float 和整数字节序是小端字节序,因此有效数(也称为尾数)是 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 位,但 int 在任何 x86 C 实现上都只是 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 ,所以检查 LDBL_MANT_DIG来自 float.h,或 sizeof(long double)。所有 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;
}

compiles efficiently on gcc/clang/ICC (on 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 使 union 类型双关成为明确定义的行为,GNU C++ 也是如此。我认为 MSVC 也定义了它。

但是 memcpy 总是可移植的,所以这可能是一个更好的选择,而且在我们只需要一个元素的情况下它更容易阅读。

如果您还需要指数和符号位,结构和 long double 之间的 union 可能会很好,只是在结构末尾填充对齐会使它更大。不过,在 uint16_t 成员之前的 uint64_t 成员之后不太可能有填充。但我担心 :1:15 位域,因为 IIRC 是实现定义的,位域成员的存储顺序。

关于c - 如何在 x86-64 上将 80 位 long double 的尾数作为 int 获取,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57499986/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com