无符号64位到双转换:为什么这个算法来自g ++

时间:2014-11-07 10:08:34

标签: assembly floating-point g++ x86-64

如果我编译,则使用g ++ 4.9.2

bool int_dbl_com(const unsigned long long x, const double y)
{
    return x <= y;
}

然后汇编程序输出(对于Windows x64调用约定)是:

testq     %rcx, %rcx            # x in RCX
js        .L2
pxor      %xmm0, %xmm0
cvtsi2sdq %rcx, %xmm0
ucomisd   %xmm0, %xmm1          # y in XMM1
setae     %al
ret

命令cvtsi2sdq签名转换,第一个测试和跳转组合是检查是否%rcx < 0。如果是这样,我们会去L2,我不明白:

.L2:
movq       %rcx, %rax
andl       $1, %ecx
pxor       %xmm0, %xmm0
shrq       %rax
orq        %rcx, %rax
cvtsi2sdq  %rax, %xmm0
addsd      %xmm0, %xmm0
ucomisd    %xmm0, %xmm1
setae      %al
ret

天真地,您可以将%rcx减半,在%xmm0中转换为双倍,然后将%xmm0添加到自身以取回原始值(当然接受您&# 39;已经失去了从64位整数到64位浮点数的低阶精度。

但这不是代码的作用:它似乎保存了%rcx的最低位,然后将其返回到结果中。为什么??那么为什么这些低阶位将会丢失(或者我在这里弄错了)呢?

(无论优化如何,似乎都使用相同的算法;我在这里使用了-O3以便于查看。)

1 个答案:

答案 0 :(得分:16)

.L2:
movq       %rcx, %rax
andl       $1, %ecx       ; save the least significant bit of %rax
pxor       %xmm0, %xmm0
shrq       %rax           ; make %rax represent half the original number, as a signed value
orq        %rcx, %rax     ; “round to odd”: if the division by two above was not exact, ensure the result is odd
cvtsi2sdq  %rax, %xmm0    ; convert to floating-point
addsd      %xmm0, %xmm0   ; multiply by two
ucomisd    %xmm0, %xmm1   ; compare …
setae      %al
ret

最后三条指令从源代码实现<=return。其他的都是从uint64_tdouble的转换的一部分。

难以理解的步骤是我评论为“圆到奇”的步骤。 “舍入到奇数”是一种可以防止“double rounding”令人讨厌的效果的技术。

实际上,算法是将64位转换为63位,然后从63位转换为IEEE 754 binary64的53位有效位。如果天真地实现,在某些情况下,这两个转换可以产生一个结果,该结果不同于从64位整数到具有53位有效数的浮点的直接单个转换。这种现象就是所谓的“双舍入”。

Rounding to odd ensures中间舍入的结果不是在双舍入的情况下将在错误的方向上舍入的值。这足以使所有输入的序列低于等效值:

64-bit ---(round to odd)---> 63-bit ---(round to nearest even)----> binary64 
64-bit -(round-to-nearest-even,the conversion the compiler wants)-> binary64

回答问题的其他方面:

  

但这不是代码的作用:它似乎保存了%rcx的最低位,然后将其返回到结果中。为什么??那么为什么这些低阶位将会丢失(或者我在这里弄错了)呢?

这正是在这个特定实例中实现round-to-odd的方法。 %rcx的最低有效位是一个如果移位不是精确除以2,在这种情况下,结果必须是奇数。

  

无论优化如何,似乎都使用相同的算法;我在这里使用了-O3以便于查看。

指令序列是最佳的(据我所知,对于现代处理器而言)并且对应于从uint64_t int到double的源级转换。即使在最低优化级别,编译器也不需要使用它。优化会发生什么(但这里不会发生)是指令与其他与其他源级构造相对应的指令融合在一起。但是,在-O0处为转换生成一个不同的指令序列是没有意义的。