簡體   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