1

double、100.00 の倍数を取り、整数に変換できる x86 インライン asm ソリューションはありますか。「入力」ダブルは事実上価格であり、整数として「セント」に変換したいと思います。

可能な仮定。

  • double は NaN、Infinity、または符号付きゼロにはなりません。
  • double は正になります
  • 変換には丸めが必要な場合があります。例: 8.19999 は、整数として 820 になる必要があります。
  • SSE4命令が利用可能です
  • データは連続して到着します
  • GCC >=4.7 が最適なコンパイラです。

別の言い方をすれば、gcc 4.7.x を使用し、-O3、--fast-math でコンパイルする場合、このタイプのコードよりも優れた x86 asm アプローチはありますか?

#include <math.h>
int cents = llround(price*100.0);
4

1 に答える 1

1

入力が 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 から離れて丸められるというプロパティを放棄する必要があります。double0.1250.6250x1.fffffffffffffp-2

タイを説明するための私の例が であり、 ではない理由を知っ0.1250.005いますよね? そうでない場合は、気にしないでください。

于 2013-06-01T17:23:35.070 に答える