c++assemblyx86-64ssefloating-point-conversion

On uint64 to double conversion: Why is the code simpler after a shift right by 1?


Why is AsDouble1 much more straightforward than AsDouble0?

// AsDouble0(unsigned long):                          # @AsDouble0(unsigned long)
//         movq    xmm1, rdi
//         punpckldq       xmm1, xmmword ptr [rip + .LCPI0_0] # xmm1 = xmm1[0],mem[0],xmm1[1],mem[1]
//         subpd   xmm1, xmmword ptr [rip + .LCPI0_1]
//         movapd  xmm0, xmm1
//         unpckhpd        xmm0, xmm1                      # xmm0 = xmm0[1],xmm1[1]
//         addsd   xmm0, xmm1
//         addsd   xmm0, xmm0
//         ret
double AsDouble0(uint64_t x) { return x * 2.0; }

// AsDouble1(unsigned long):                          # @AsDouble1(unsigned long)
//         shr     rdi
//         cvtsi2sd        xmm0, rdi
//         addsd   xmm0, xmm0
//         ret
double AsDouble1(uint64_t x) { return (x >> 1) * 2.0; }

Code available at: https://godbolt.org/z/dKc6Pe6M1


Solution

  • x86 has an instruction to convert between signed integers and floats. Unsigned integer conversion is (I think) supported by AVX512, which most compilers don't assume by default. If you shift right a uint64_t once, the sign bit is gone, so you can interpret it as a signed integer and have the same result.