`uint64_t`有什么难点?(从`float`进行转换的汇编)

11

我面临这样的情况:需要计算类似于size_t s=(size_t)floorf(f);这样的东西。也就是说,参数是浮点数,但它有一个整数值(假设floorf(f)足够小以被准确表示)。在优化过程中,我发现了一些有趣的东西。

这里是从float到整数的一些转换(使用GCC 5.2.0 -O3进行测试)。为了清晰起见,给出的转换是测试函数的返回值。

下面是int32_t x=(int32_t)f

    cvttss2si   eax, xmm0
    ret

这里是uint32_t x=(uint32_t)f

    cvttss2si   rax, xmm0
    ret

这里是 int64_t x=(int64_t)f

    cvttss2si   rax, xmm0
    ret

最后,这里有uint64_t x=(uint64_t)f;

    ucomiss xmm0, DWORD PTR .LC2[rip]
    jnb .L4
    cvttss2si   rax, xmm0
    ret
.L4:
    subss   xmm0, DWORD PTR .LC2[rip]
    movabs  rdx, -9223372036854775808
    cvttss2si   rax, xmm0
    xor rax, rdx
    ret

.LC2:
    .long   1593835520

这最后一个比其他的要复杂得多。此外,Clang和MSVC的行为类似。为了方便起见,我已将其翻译成伪代码:

float lc2 = (float)(/* 2^63 - 1 */);
if (f<lc2) {
    return (uint64_t)f;
} else {
    f -= lc2;
    uint64_t temp = (uint64_t)f;
    temp ^= /* 2^63 */; //Toggle highest bit
    return temp;
}

这似乎是在尝试正确计算第一个溢出模64。这似乎有点荒谬,因为cvttss2si的文档告诉我,如果发生溢出(在2^32而不是2 ^ 64处),"返回无限整数值(80000000H)"。

我的问题:

  1. 这实际上是在做什么,以及为什么要这样做?
  2. 为什么其他整数类型没有类似的处理方式?
  3. 如何更改转换以产生类似的代码(仅输出3和4行)(再次假设该值完全可表示)?

这篇博客文章,特别是这个评论与你的问题有关:http://blog.frama-c.com/index.php?post/2013/10/09/Overflow-float-integer#c379 - Pascal Cuoq
1个回答

12

由于cvttss2si进行有符号转换,因此它将认为区间[2^63,2^64)中的数字超出范围,而实际上这些数字对于无符号来说是在范围内的。因此,会检测到这种情况并将其映射到浮点数的低半部分,并在转换后应用校正。

至于其他情况,请注意uint32_t转换仍使用一个64位目标,这将适用于uint32_t的全部范围,而使用结果的低32位隐含地进行截断,根据调用约定。

关于避免额外代码方面,这取决于您的输入是否可能落入上述提到的范围内。如果可能,则没有任何方法可以避免。否则,首先进行双重转换,即(uint64_t)(int64_t)f,然后再转换为无符号。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接