如果我编译,则使用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以便于查看。)
答案 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_t
到double
的转换的一部分。
难以理解的步骤是我评论为“圆到奇”的步骤。 “舍入到奇数”是一种可以防止“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
处为转换生成一个不同的指令序列是没有意义的。