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