`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".
我的问题:
- 这到底在做什么,为什么?
- 为什么没有对其他整数类型做类似的事情?
- 如何更改转换以生成类似的代码(仅输出第 3 行和第 4 行)(再次假设该值是可精确表示的)?
由于 cvttss2si
进行有符号转换,它将认为区间 [2^63, 2^64)
中的数字超出范围,而实际上它们在无符号范围内。因此,检测到这种情况并将其映射到浮点数的低半部分,并在转换后应用校正。
至于其他情况,请注意 uint32_t
转换仍然使用 64 位目标,这将适用于 uint32_t
的整个范围,并且通过使用低 32 隐式进一步截断根据调用约定的结果位。
在避免额外代码方面,取决于您的输入是否属于上述范围。如果可以,那就没有办法了。否则,先对 signed 然后再对 unsigned 进行双重转换是可行的,即。 (uint64_t)(int64_t)f
.
我处于需要计算 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".
我的问题:
- 这到底在做什么,为什么?
- 为什么没有对其他整数类型做类似的事情?
- 如何更改转换以生成类似的代码(仅输出第 3 行和第 4 行)(再次假设该值是可精确表示的)?
由于 cvttss2si
进行有符号转换,它将认为区间 [2^63, 2^64)
中的数字超出范围,而实际上它们在无符号范围内。因此,检测到这种情况并将其映射到浮点数的低半部分,并在转换后应用校正。
至于其他情况,请注意 uint32_t
转换仍然使用 64 位目标,这将适用于 uint32_t
的整个范围,并且通过使用低 32 隐式进一步截断根据调用约定的结果位。
在避免额外代码方面,取决于您的输入是否属于上述范围。如果可以,那就没有办法了。否则,先对 signed 然后再对 unsigned 进行双重转换是可行的,即。 (uint64_t)(int64_t)f
.