Search code examples
assemblyfloating-pointssefloating-point-conversionx87

Are there unsigned equivalents of the x87 FILD and SSE CVTSI2SD instructions?


I want to implement the equivalent of C's uint-to-double cast in the GHC Haskell compiler. We already implement int-to-double using FILD or CVTSI2SD. Is there unsigned versions of these operations or am I supposed to zero out the highest bit of the uint before the conversion (thus losing range)?


Solution

  • You can exploit some of the properties of the IEEE double format and interpret the unsigned value as part of the mantissa, while adding some carefully crafted exponent.

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

    The 1075 comes from the IEEE exponent bias (1023) for doubles and a "shift" amount of 52 bits for your mantissa. Note that there is a implicit "1" leading the mantissa, which needs to be subtracted later.

    So:

    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
    }
    

    If you don't have native 64 bit on you platform a version using SSE for the integer steps might be beneficial, but that depends of course.

    On my platform this compiles to

    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
    

    which looks pretty good. The 0x0(%rip) is the magic double constant, and if inlined some instructions like the upper 32 bit zeroing and the constant reload will vanish.