是否有未签名的x87 FILD和SSE CVTSI2SD指令?

时间:2012-12-05 23:09:00

标签: assembly floating-point sse x87

我想在GHC Haskell编译器中实现等效的C uint - to - double强制转换。我们已使用intdouble实施FILD - 至 - CVTSI2SD。是否存在这些操作的无符号版本,或者我应该在转换之前将uint的最高位归零(从而丢失范围)?

5 个答案:

答案 0 :(得分:4)

您可以利用IEEE双精度格式的某些属性,并将无符号值解释为尾数的一部分,同时添加一些精心设计的指数。

Bits 63 62-52     51-0
     S  Exp       Mantissa
     0  1075      20 bits 0, followed by your unsigned int

1075来自双指数的IEEE指数偏差(1023)和尾数的52位“移位”量。请注意,有一个隐含的“1”引导尾数,需要在以后减去。

所以:

double uint32_to_double(uint32_t x) {
    uint64_t xx = x;
    xx += 1075ULL << 52;         // add the exponent
    double d = *(double*)&xx;    // or use a union to convert
    return d - (1ULL << 52);     // 2 ^^ 52
}

如果您的平台上没有本机64位,则使用SSE进行整数步骤的版本可能会有所帮助,但这当然取决于此。

在我的平台上,这将编译为

0000000000000000 <uint32_to_double>:
   0:   48 b8 00 00 00 00 00    movabs $0x4330000000000000,%rax
   7:   00 30 43 
   a:   89 ff                   mov    %edi,%edi
   c:   48 01 f8                add    %rdi,%rax
   f:   c4 e1 f9 6e c0          vmovq  %rax,%xmm0
  14:   c5 fb 5c 05 00 00 00    vsubsd 0x0(%rip),%xmm0,%xmm0 
  1b:   00 
  1c:   c3                      retq

看起来很不错。 0x0(%rip)是神奇的双常数,如果内联一些指令,如高32位归零和常量重载将消失。

答案 1 :(得分:3)

有一种更好的方法

__m128d _mm_cvtsu32_sd(__m128i n) {
    const __m128i magic_mask = _mm_set_epi32(0, 0, 0x43300000, 0);
    const __m128d magic_bias = _mm_set_sd(4503599627370496.0);
    return _mm_sub_sd(_mm_castsi128_pd(_mm_or_si128(n, magic_mask)), magic_bias);
}

答案 2 :(得分:3)

有人说,&#34;好艺术家复制;伟大的艺术家窃取&#34;。所以我们可以检查其他编译器编写者如何解决这个问题。我使用了一个简单的片段:

volatile unsigned int x;
int main()
{
  volatile double  y = x;
  return y;
}

(添加挥发物以确保编译器不会优化转换)

结果(跳过不相关的指示):

Visual C ++ 2010 cl / Ox(x86)

  __real@41f0000000000000 DQ 041f0000000000000r ; 4.29497e+009

  mov   eax, DWORD PTR ?x@@3IC          ; x
  fild  DWORD PTR ?x@@3IC           ; x
  test  eax, eax
  jns   SHORT $LN4@main
  fadd  QWORD PTR __real@41f0000000000000
$LN4@main:
  fstp  QWORD PTR _y$[esp+8]

所以基本上编译器在设置符号位时添加调整值。

Visual C ++ 2010 cl / Ox(x64)

  mov   eax, DWORD PTR ?x@@3IC          ; x
  pxor  xmm0, xmm0
  cvtsi2sd xmm0, rax
  movsdx    QWORD PTR y$[rsp], xmm0

无需在此调整,因为编译器知道rax将清除符号位。

Visual C ++ 2012 cl / Ox

  __xmm@41f00000000000000000000000000000 DB 00H, 00H, 00H, 00H, 00H, 00H, 00H
  DB 00H, 00H, 00H, 00H, 00H, 00H, 00H, 0f0H, 'A'

  mov   eax, DWORD PTR ?x@@3IC          ; x
  movd  xmm0, eax
  cvtdq2pd xmm0, xmm0
  shr   eax, 31                 ; 0000001fH
  addsd xmm0, QWORD PTR __xmm@41f00000000000000000000000000000[eax*8]
  movsd QWORD PTR _y$[esp+8], xmm0

这使用无分支代码添加0或魔术调整,具体取决于符号位是清除还是设置。

答案 3 :(得分:2)

  

我们已经使用FILD实现了int-to-double ...
  是否有这些操作的无符号版本

如果你想要使用x87 FILD操作码,只需将uint64转换为uint63(div 2),然后将其转换为2,但已经为double,因此x87 uint64-to-double转换需要在头顶上执行一次FMUL

示例: 0xFFFFFFFFFFFFFFFFU - &gt; + 1.8446744073709551e + 0019

它无法在严格的表单规则中发布代码示例。我稍后再试。

    //inline
    double    u64_to_d(unsigned _int64 v){

    //volatile double   res;
    volatile unsigned int tmp=2;
    _asm{
    fild  dword ptr tmp
    //v>>=1;
    shr   dword ptr v+4, 1
    rcr   dword ptr v, 1
    fild  qword ptr v

    //save lsb
    //mov   byte ptr tmp, 0  
    //rcl   byte ptr tmp, 1

    //res=tmp+res*2;
    fmulp st(1),st
    //fild  dword ptr tmp
    //faddp st(1),st 

    //fstp  qword ptr res
    }

    //return res;
    //fld  qword ptr res
}

VC产生x86输出

        //inline
        double    u64_to_d(unsigned _int64 v){
    55                   push        ebp  
    8B EC                mov         ebp,esp  
    81 EC 04 00 00 00    sub         esp,04h  

        //volatile double   res;
        volatile unsigned int tmp=2;
    C7 45 FC 02 00 00 00 mov         dword ptr [tmp], 2  
        _asm{
        fild  dword ptr tmp
    DB 45 FC             fild        dword ptr [tmp]  
        //v>>=1;
        shr   dword ptr v+4, 1
    D1 6D 0C             shr         dword ptr [ebp+0Ch],1  
        rcr   dword ptr v, 1
    D1 5D 08             rcr         dword ptr [v],1  
        fild  qword ptr v
    DF 6D 08             fild        qword ptr [v]  

        //save lsb
    //    mov   byte ptr [tmp], 0  
    //C6 45 FC 00        mov         byte ptr [tmp], 0
    //    rcl   byte ptr tmp, 1
    //D0 55 FC           rcl         byte ptr [tmp],1  

        //res=tmp+res*2;
        fmulp st(1),st
    DE C9                fmulp       st(1),st  
    //    fild  dword ptr tmp
    //DB 45 FC           fild        dword ptr [tmp]  
    //    faddp st(1),st 
    //DE C1              faddp       st(1),st  


        //fstp  qword ptr res
        //fstp        qword ptr [res]  
    }

        //return res;
        //fld         qword ptr [res]  

    8B E5                mov         esp,ebp  
    5D                   pop         ebp  
    C3                   ret  
}

我发布了(可能是我手动删除了文本文件中所有未修正的ascii字符)。

答案 4 :(得分:1)

如果我正确理解你,你应该能够将32位uint移动到堆栈上的临时区域,将下一个dword归零,然后使用fild qword ptr将现在的64位无符号整数加载为双