はじめに
フツーはやらないと思いますが、
uint64_t double2uint64_t(double x)
{
return (uint64_t)x;
}
こういうやつですね、gcc が uint32_t や int64_t への変換に比べて残念なコード吐くのに気付いたのでメモ。
#include <stdint.h>
uint32_t double2uint32_t(double x)
{
return (uint32_t)x;
}
int64_t double2int64_t(double x)
{
return (int64_t)x;
}
uint64_t double2uint64_t(double x)
{
return (uint64_t)x;
}
double2uint32_t:
cvttsd2siq %xmm0, %rax
ret
double2int64_t:
cvttsd2siq %xmm0, %rax
ret
double2uint64_t:
movsd .LC0(%rip), %xmm1
comisd %xmm1, %xmm0
jnb .L5
cvttsd2siq %xmm0, %rax
ret
.L5:
subsd %xmm1, %xmm0
cvttsd2siq %xmm0, %rax
btcq $63, %rax
ret
.LC0:
.long 0
.long 1138753536
どうしてこうなった
x86-64 では double から uint32_t や int64_t へは 1命令で変換できる命令があるが、uint64_t へはそういう命令がない、ということですね。
あ、ひとつウソ書きました。x86-64 でも AVX-512 に対応しているプロセッサであれば double を uint64_t に 1命令で変換することができるようです。
double2uint64_t:
vcvttsd2usi %xmm0, %rax
ret
で、この煩いコードは何やってんの?
gcc で下記のソースをコンパイルすると凡そ同等のコードを吐かせることができます。
#include <stdint.h>
uint64_t double2uint64_t(double x)
{
if (x <= INT64_MAX) {
// int64_t の最大値以下であれば int64_t へ変換
return (int64_t)x;
} else {
// int64_t の最大値より大きければ int64_t の最大値+1 の値を引き int64_t へ変換
int64_t y = x - (INT64_MAX + 1UL);
// MSB を反転することでさっき引いた int64_t の最大値+1 分を足したのと同じにする
return y ^ (1UL << 63);
}
}
double2uint64_t:
vmovsd .LC0(%rip), %xmm1
vcomisd %xmm0, %xmm1
jb .L6
vcvttsd2siq %xmm0, %rax
ret
.L6:
vsubsd %xmm1, %xmm0, %xmm0
vcvttsd2siq %xmm0, %rax
btcq $63, %rax
ret
.LC0:
.long 0
.long 1138753536
要は、元の値が 0~INT64_MAX の範囲であれば double → int64_t の命令を使用して変換、それより大きい場合は INT64_MAX+1 分ゲタ履いてることにして double → int64_t 変換するということです。
なぜ気付いた
こちらの記事で x86-64 用に不思議なコードを吐かせているのを見掛けたことがきっかけでした。株式会社フィックスターズ様と記事を書かれた方には感謝いたします。
おわりに
おわりです。