double の数値が整数かどうかをテストする最速の方法は何ですか (最新のインテル X86 プロセッサーの場合)

StackOverflow https://stackoverflow.com/questions/1944119

質問

私たちのサーバー アプリケーションは、ホット コード パスで多くの整数テストを実行します。現在、次の関数を使用しています。

inline int IsInteger(double n)
{
    return n-floor(n) < 1e-8
}

この関数はワークロードの中で非常にホットなため、できるだけ高速にする必要があります。また、できれば「フロア」ライブラリ呼び出しも排除したいと考えています。助言がありますか?

他のヒント

ここでは、回答のカップルです:

#include <stdint.h>
#include <stdio.h>
#include <math.h>

int IsInteger1(double n)
{
  union
  {
        uint64_t i;
        double d;
  } u;
  u.d = n;

  int exponent = ((u.i >> 52) & 0x7FF) - 1023;
  uint64_t mantissa = (u.i & 0x000FFFFFFFFFFFFFllu);

  return n == 0.0 ||
        exponent >= 52 ||
        (exponent >= 0 && (mantissa << (12 + exponent)) == 0);
}

int IsInteger2(double n)
{
  return n - (double)(int)n == 0.0;
}

int IsInteger3(double n)
{
  return n - floor(n) == 0.0;
}

とテストハーネスます:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

int IsInteger1(double);
int IsInteger2(double);
int IsInteger3(double);

#define TIMEIT(expr, N) \
  gettimeofday(&start, NULL); \
  for(i = 0; i < N; i++) \
  { \
    expr; \
  } \
  gettimeofday(&end, NULL); \
  printf("%s: %f\n", #expr, (end.tv_sec - start.tv_sec) + 0.000001 * (end.tv_usec - start.tv_usec))

int main(int argc, char **argv)
{
  const int N = 100000000;
  struct timeval start, end;
  int i;

  double d = strtod(argv[1], NULL);
  printf("d=%lf %d %d %d\n", d, IsInteger(d), IsInteger2(d), IsInteger3(d));

  TIMEIT((void)0, N);
  TIMEIT(IsInteger1(d), N);
  TIMEIT(IsInteger2(d), N);
  TIMEIT(IsInteger3(d), N);

  return 0;
}

としてコンパイル

gcc isinteger.c -O3 -c -o isinteger.o
gcc main.c isinteger.o -o isinteger

私の結果、インテルCore Duoプロセッサ上:

$ ./isinteger 12345
d=12345.000000 1 1 1
(void)0: 0.357215
IsInteger1(d): 2.017716
IsInteger2(d): 1.158590
IsInteger3(d): 2.746216

結論:ビットいじるが、私は推測しているかもしれないほど高速ではありません。余分な枝は、浮動小数点演算を回避していても、それを殺すおそらく何です。 FPUはdoubleツーint変換やfloorを行うことは本当に遅くはないことを、これらの日は十分に高速です。

もし doubleマシン上の は IEEE-754 に準拠しており、この共用体は double のレイアウトを記述します。

union
{
   double d;
   struct
   {
       int sign     :1
       int exponent :11
       int mantissa :52
   };
} double_breakdown;

これにより、double が整数を表すかどうかがわかります。

免責事項 1:私は言っています 整数, 、ではありません int, 、double は整数ではあるが、その大きさが大きすぎてファイルに格納できない数値を表すことができるため、 int.

免責事項 2:Double は、実数に最も近い値を保持します。したがって、これはおそらく、 代表される 数字は整数を形成します。たとえば、非常に大きな double は、小数値を表すのに十分な有効桁数がないため、常に整数になります。

bool is_integer( double d )
{
    const int exponent_offset = 1023;
    const int mantissa_bits = 52;

    double_breakdown *db = &d;

    // See if exponent is too large to hold a decimal value.
    if ( db->exponent >= exponent_offset + mantissa_bits )
       return true;  // d can't represent non-integers

    // See if exponent is too small to hold a magnitude greater than 1.0.
    if ( db->exponent <= exponent_offset )
       return false; // d can't represent integers

    // Return whether any mantissa bits below the decimal point are set.
    return ( db->mantissa << db->exponent - exponent_offset == 0 );
}
あなたが本当にハック取得したい場合は、

IEEE 754 の仕様を参照してください。浮動小数点数は、有効数字と指数として実装されています。私はそれを行う方法を正確にわからないんだけど、あなたはおそらくのような何かを行うことができます:

union {
    float f;
    unsigned int i;
}

これは有効数字と指数の両方へのアクセスをビット単位になるだろう。そして、あなたの周りのあなたの方法をビットひねり可能性があります。

別の方法:

inline int IsInteger(double n)
{
    double dummy;
    return modf(n, &dummy) == 0.0;
}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top