`uint64_t` 有什么难的? (从 `float` 转换程序集)

What's So Difficult About `uint64_t`? (Conversion Assembly From `float`)

我处于需要计算 size_t s=(size_t)floorf(f); 之类的情况。也就是说,参数是一个浮点数,但它有一个整数值(假设 floorf(f) 足够小,可以精确表示)。在优化的过程中,我发现了一些有趣的事情。

这里是从 float 到整数 (GCC 5.2.0 -O3) 的一些转换。为清楚起见,给出的转换是测试函数的 return 值。

这是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 的行为相似。为了方便起见,我把它翻译成了伪C:

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;
}

这看起来像是在尝试正确计算第一个溢出 mod 64。这似乎有点虚假,因为 the documentation for cvttss2si 告诉我如果发生溢出(在 2^32,而不是 2^64),"the indefinite integer value (80000000H) is returned".

我的问题:

  1. 这到底在做什么,为什么?
  2. 为什么没有对其他整数类型做类似的事情?
  3. 如何更改转换以生成类似的代码(仅输出第 3 行和第 4 行)(再次假设该值是可精确表示的)?

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

至于其他情况,请注意 uint32_t 转换仍然使用 64 位目标,这将适用于 uint32_t 的整个范围,并且通过使用低 32 隐式进一步截断根据调用约定的结果位。

在避免额外代码方面,取决于您的输入是否属于上述范围。如果可以,那就没有办法了。否则,先对 signed 然后再对 unsigned 进行双重转换是可行的,即。 (uint64_t)(int64_t)f.