Подтвердить что ты не робот

Какой самый быстрый способ проверить, является ли двойной номер целым (в современных процессорах Intel X86)

Наше серверное приложение выполняет множество целых тестов в режиме горячего кода, в настоящее время мы используем следующую функцию:

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

Эта функция очень горячая в нашей рабочей нагрузке, поэтому я хочу, чтобы она была как можно быстрее. Я также хочу исключить библиотечный вызов "floor", если смогу. Любые предложения?

4b9b3361

Ответ 1

A while back Я провел кучу таймингов по наиболее эффективному способу конвертировать между поплавками и целыми числами и написал их. Я также приуроченные методы округления.

Короткий рассказ для вас: преобразование из поплавка в int или использование хакеров, вряд ли будет улучшением из-за опасности процессора, называемой магазином с нагрузкой - если только поплавки не поступают из ОЗУ а не реестр.

Поскольку это внутреннее, abs (floor (x) -eps), вероятно, является самым быстрым решением. Но поскольку это очень чувствительно к конкретной архитектуре вашего процессора - в зависимости от очень чувствительных вещей, таких как глубина трубопровода и пересылки хранилища, вам нужно будет найти множество решений, чтобы найти тот, который действительно оптимален.

Ответ 2

Вот несколько ответов:

#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

Мои результаты на Intel 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 -to- int или floor действительно не так медленно.

Ответ 3

Если double на вашем компьютере соответствует IEEE-754, этот союз описывает двойной макет.

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

Это скажет вам, является ли double двойным целым.

Отказ от ответственности 1: Я говорю целое, а не int, поскольку double может представлять числа, которые являются целыми числами, но значения которых слишком велики для хранения в int.

Отказ от ответственности 2: Удвоенные пары будут удерживать самое близкое возможное значение до любого действительного числа. Таким образом, это может только вернуть, представляют ли представленные цифры целое число. Чрезвычайно большие двойники, например, всегда целые числа, потому что у них недостаточно значащих цифр для представления какого-либо дробного значения.

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 );
}

Ответ 4

Если вы действительно хотите стать хакерским, см. описание IEEE 754. Числа с плавающей запятой реализуются как значение и показатель. Я не уверен, как это сделать, но вы, вероятно, могли бы сделать что-то вроде:

union {
    float f;
    unsigned int i;
}

Это даст вам побитовый доступ как к значению, так и к экспоненте. Тогда вы можете немного покрутиться.

Ответ 5

Другая альтернатива:

inline int IsInteger(double n)
{
    double dummy;
    return modf(n, &dummy) == 0.0;
}