入力が 2^52/100 未満であると仮定して変換関数を書きます。
#include <string.h>
#include <stdio.h>
/*@ requires 0 <= d < 0x1.0p52 ; */
long long cents(double d)
{
d = d * 100. + 0x1.0p52;
long long l;
memcpy(&l, &d, sizeof(double));
return l & 0xFFFFFFFFFFFFF;
}
int main()
{
printf("%lld\n", cents(0.994));
printf("%lld\n", cents(0.996));
printf("%lld\n", cents(123456789.004));
printf("%lld\n", cents(123456789.006));
}
期待される結果は次のとおりです。
99
100
12345678900
12345678901
gcc -O2
関数の計算部分を次のようにコンパイルしますcents()
。
mulsd LCPI1_0(%rip), %xmm0
addsd LCPI1_1(%rip), %xmm0
movd %xmm0, %rcx
movabsq $4503599627370495, %rax
andq %rcx, %rax
インライン化するか、コンパイラにインライン化するように指示することができます。llround()
これは、プロセッサによっては、より速い場合とそうでない場合があります。
融合乗算加算命令を使用d * 100. + 0x1.0p52
できる場合は、単一の命令で計算できますが、とにかく定数をロードするコストがかかります。これらの多くをループで実行する必要がある場合は、定数をレジスターに残します (またはコンパイラーにそうできることを伝えます)。
0x1.fffffffffffffp-2
別の方法として、 (double
すぐ下の )を追加し、次のよう0.5
に切り捨てlong long
ます。
long long cents(double d) { return d * 100. + 0x1.fffffffffffffp-2; }
0x1.fffffffffffffp-2
の代わりに使用する0.5
理由は、整数が存在するすべての場合に最も近い整数を提供することです。対照的に、追加する0.5
と、場合によっては最も近い 2 つの整数の最も遠いものが得られます (この投稿では、float
の代わりにtype を使用して詳細を説明します)。引き換えに、タイ ( 、、…) が 0 から離れて丸められるというプロパティを放棄する必要があります。double
0.125
0.625
0x1.fffffffffffffp-2
タイを説明するための私の例が であり、 ではない理由を知っ0.125
て0.005
いますよね? そうでない場合は、気にしないでください。