Быстрое вычисление log2 для 64-битных целых чисел

Отличный ресурс программирования, бит Twiddling Hacks, предлагает (здесь) следующий метод для вычисления log2 32-разрядного целого:

#define LT(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n
static const char LogTable256[256] = 
{
    -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
    LT(4), LT(5), LT(5), LT(6), LT(6), LT(6), LT(6),
    LT(7), LT(7), LT(7), LT(7), LT(7), LT(7), LT(7), LT(7)
};

unsigned int v; // 32-bit word to find the log of
unsigned r;     // r will be lg(v)
register unsigned int t, tt; // temporaries
if (tt = v >> 16)
{
    r = (t = tt >> 8) ? 24 + LogTable256[t] : 16 + LogTable256[tt];
}
else 
{
    r = (t = v >> 8) ? 8 + LogTable256[t] : LogTable256[v];
}

и упоминает, что

В методе таблицы поиска используется только около 7 операций для поиска журнала 32-битного значения. Если он расширен для 64-битных количеств, примерно 9 операций.

но, увы, не дает никакой дополнительной информации о том, каким образом следует перейти к расширению алгоритма до 64-битных целых чисел.

Любые подсказки о том, как выглядит такой 64-битный алгоритм?

Ответ 1

Внутренние функции действительно быстрые, но все же недостаточны для действительно кроссплатформенной, независимой от компилятора реализации log2. Так что, если кому-то это интересно, вот самый быстрый, без ветвлений, абстрактный процессором алгоритм, похожий на DeBruijn, к которому я пришел, исследуя эту тему самостоятельно.

const int tab64[64] = {
    63,  0, 58,  1, 59, 47, 53,  2,
    60, 39, 48, 27, 54, 33, 42,  3,
    61, 51, 37, 40, 49, 18, 28, 20,
    55, 30, 34, 11, 43, 14, 22,  4,
    62, 57, 46, 52, 38, 26, 32, 41,
    50, 36, 17, 19, 29, 10, 13, 21,
    56, 45, 25, 31, 35, 16,  9, 12,
    44, 24, 15,  8, 23,  7,  6,  5};

int log2_64 (uint64_t value)
{
    value |= value >> 1;
    value |= value >> 2;
    value |= value >> 4;
    value |= value >> 8;
    value |= value >> 16;
    value |= value >> 32;
    return tab64[((uint64_t)((value - (value >> 1))*0x07EDD5E59A4E28C2)) >> 58];
}

Часть округления до следующей более низкой степени 2 была взята из Границы степени 2, а часть для получения числа конечных нулей была взята из BitScan (код (bb & -bb) позволяет выделить самый правый бит, установленный в 1, который не нужен после того, как мы округлили значение до следующей степени 2).

И 32-битная реализация, кстати, является

const int tab32[32] = {
     0,  9,  1, 10, 13, 21,  2, 29,
    11, 14, 16, 18, 22, 25,  3, 30,
     8, 12, 20, 28, 15, 17, 24,  7,
    19, 27, 23,  6, 26,  5,  4, 31};

int log2_32 (uint32_t value)
{
    value |= value >> 1;
    value |= value >> 2;
    value |= value >> 4;
    value |= value >> 8;
    value |= value >> 16;
    return tab32[(uint32_t)(value*0x07C4ACDD) >> 27];
}

Как и в любом другом вычислительном методе, log2 требует, чтобы входное значение было больше нуля.

Ответ 2

Если вы используете GCC, в этом случае таблица поиска не нужна.

GCC предоставляет встроенную функцию для определения количества начальных нулей:

Встроенная функция: int __builtin_clz (unsigned int x)
Возвращает число начальных 0-бит в x, начиная с самой значительной битовой позиции. Если x равно 0, результат равен undefined.

Итак, вы можете определить:

#define LOG2(X) ((unsigned) (8*sizeof (unsigned long long) - __builtin_clzll((X)) - 1))

и он будет работать для любого беззнакового long long int. Результат округляется.

Для x86 и AMD64 GCC будет компилировать его в команду bsr, поэтому решение выполняется очень быстро (намного быстрее, чем таблицы поиска).

Рабочий пример:

#include <stdio.h>

#define LOG2(X) ((unsigned) (8*sizeof (unsigned long long) - __builtin_clzll((X)) - 1))

int main(void) {
    unsigned long long input;
    while (scanf("%llu", &input) == 1) {
        printf("log(%llu) = %u\n", input, LOG2(input));
    }
    return 0;
}

Ответ 3

Я пытался преобразовать Найти базу данных 2 из N-разрядного целого числа в операциях O (lg (N)) с умножением и поиском до 64-битного путем грубого форсирования магического числа. Излишне говорить, что это заняло некоторое время.

Затем я нашел ответ Десмонда и решил попробовать его магическое число в качестве начальной точки. Поскольку у меня есть 6-ядерный процессор, я запускал его параллельно, начиная с 0x07EDD5E59A4E28C2/6. Я был удивлен, что нашел что-то сразу. Выключается 0x07EDD5E59A4E28C2/2.

Итак, вот код для 0x07EDD5E59A4E28C2, который сэкономит вам смещение и вычитает:

int LogBase2(uint64_t n)
{
    static const int table[64] = {
        0, 58, 1, 59, 47, 53, 2, 60, 39, 48, 27, 54, 33, 42, 3, 61,
        51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22, 4, 62,
        57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21, 56,
        45, 25, 31, 35, 16, 9, 12, 44, 24, 15, 8, 23, 7, 6, 5, 63 };

    n |= n >> 1;
    n |= n >> 2;
    n |= n >> 4;
    n |= n >> 8;
    n |= n >> 16;
    n |= n >> 32;

    return table[(n * 0x03f6eaf2cd271461) >> 58];
}

Ответ 4

Логарифм Integer с базой-2

Вот что я делаю для 64-разрядных целых чисел без знака. Это вычисляет пол логарифма базы-2, что эквивалентно индексу самого значимого бита. Этот метод является курьезно быстрым для больших чисел, поскольку он использует развернутый цикл, который выполняется всегда в log₂64 = 6 шагов.

По существу, то, что он делает, вычитает постепенно меньшие квадраты в последовательности {0 ≤ k ≤ 5: 2 ^ (2 ^ k)} = {2³², 2¹⁶, 2⁸, 2⁴, 2², 2¹} = {4294967296, 65536, 256, 16, 4, 2, 1} и суммирует показатели k вычитаемых значений.

int uint64_log2(uint64_t n)
{
  #define S(k) if (n >= (UINT64_C(1) << k)) { i += k; n >>= k; }

  int i = -(n == 0); S(32); S(16); S(8); S(4); S(2); S(1); return i;

  #undef S
}

Обратите внимание, что это возвращает -1, если задан недопустимый ввод 0 (это то, что проверяет начальный -(n == 0)). Если вы никогда не ожидаете вызвать его с помощью n == 0, вы можете подставить int i = 0; для инициализатора и добавить assert(n != 0); при входе в функцию.

Логарифм Integer с базой-10

Локарифмы целочисленного значения Base-10 могут быть вычислены аналогичным образом - с наибольшим квадратом для теста будет 10¹⁶, потому что log₁₀2⁶⁴ ≅ 19.2659... (Примечание. Это не самый быстрый способ выполнить логарифм с целым числом 10, поскольку он использует целочисленное деление, которое по своей сути медленное. Более быстрая реализация заключалась бы в использовании аккумулятора со значениями, которые экспоненциально растут и сравниваются с аккумулятором, фактически выполняющим двоичный поиск.)

int uint64_log10(uint64_t n)
{
  #define S(k, m) if (n >= UINT64_C(m)) { i += k; n /= UINT64_C(m); }

  int i = -(n == 0);
  S(16,10000000000000000); S(8,100000000); S(4,10000); S(2,100); S(1,10);
  return i;

  #undef S
}

Ответ 5

Здесь довольно компактное и быстрое расширение, не использующее дополнительных временных рядов:

r = 0;

/* If its wider than 32 bits, then we already know that log >= 32.
So store it in R.  */
if (v >> 32)
  {
    r = 32;
    v >>= 32;
  }

/* Now do the exact same thing as the 32 bit algorithm,
except we ADD to R this time.  */
if (tt = v >> 16)
  {
    r += (t = tt >> 8) ? 24 + LogTable256[t] : 16 + LogTable256[tt];
  }
else
  {
    r += (t = v >> 8) ? 8 + LogTable256[t] : LogTable256[v];
  }

Вот один построенный с цепочкой if s, опять же без дополнительных временных рядов. Может быть, не самый быстрый, хотя.

  if (tt = v >> 48)
    {
      r = (t = tt >> 8) ? 56 + LogTable256[t] : 48 + LogTable256[tt];
    }
  else if (tt = v >> 32)
    {
      r = (t = tt >> 8) ? 40 + LogTable256[t] : 32 + LogTable256[tt];
    }
  else if (tt = v >> 16)
    {
      r = (t = tt >> 8) ? 24 + LogTable256[t] : 16 + LogTable256[tt];
    }
  else 
    {
      r = (t = v >> 8) ? 8 + LogTable256[t] : LogTable256[v];
    }

Ответ 6

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

Вот несколько упрощенная версия 32-битного алгоритма:

if (tt = v >> 16)
{
    if (t = tt >> 8)
    {
        r = 24 + LogTable256[t];
    }
    else
    {
        r = 16 + LogTable256[tt];
    }
}
else 
{
    if (t = v >> 8)
    {
        r = 8 + LogTable256[t];
    }
    else
    {
        r = LogTable256[v];
    }
}

Это эквивалентный 64-битный алгоритм:

if (ttt = v >> 32)
{
    if (tt = ttt >> 16)
    {
        if (t = tt >> 8)
        {
            r = 56 + LogTable256[t];
        }
        else
        {
            r = 48 + LogTable256[tt];
        }
    }
    else 
    {
        if (t = ttt >> 8)
        {
            r = 40 + LogTable256[t];
        }
        else
        {
            r = 32 + LogTable256[ttt];
        }
    }
}
else
{
    if (tt = v >> 16)
    {
        if (t = tt >> 8)
        {
            r = 24 + LogTable256[t];
        }
        else
        {
            r = 16 + LogTable256[tt];
        }
    }
    else 
    {
        if (t = v >> 8)
        {
            r = 8 + LogTable256[t];
        }
        else
        {
            r = LogTable256[v];
        }
    }
}

Я придумал алгоритм для любых типов размеров, которые, по моему мнению, приятнее оригинала.

unsigned int v = 42;
unsigned int r = 0;
unsigned int b;
for (b = sizeof(v) << 2; b; b = b >> 1)
{
    if (v >> b)
    {
        v = v >> b;
        r += b;
    }
}

Примечание: b = sizeof(v) << 2 устанавливает b в половину числа бит в v. Я использовал вместо этого умножение вместо этого (просто потому, что мне это нравилось).

Вы можете добавить таблицу поиска к этому алгоритму, чтобы ускорить ее, но это скорее доказательство концепции.

Ответ 7

Возьми это:

typedef unsigned int uint;
typedef uint64_t ulong;
uint as_uint(const float x) {
    return *(uint*)&x;
}
ulong as_ulong(const double x) {
    return *(ulong*)&x;
}
uint log2_fast(const uint x) {
    return (as_uint((float)x)>>23)-127;
}
uint log2_fast(const ulong x) {
    return (uint)((as_ulong((double)x)>>52))-1023;
}

Как это работает: входное целое число x преобразуется в число с float а затем интерпретируется как биты. Формат с float IEEE хранит экспоненту в битах 30-23 в виде целого числа со смещением 127, поэтому, сдвинув его на 23 бита вправо и вычтя смещение, мы получим log2 (x). Для 64-разрядного целочисленного входа x приводится к double, для которого показатель степени находится в битах 62-52 (сдвиг на 52 бита вправо), а смещение показателя равно 1023.

Ответ 8

Вот слегка измененный вариант из SPWorley от 22.03.2009 (подробности см. в сообщении)

double ff=(double)(v|1);
return ((*(1+(uint32_t *)&ff))>>52)-1023;  // assumes x86 endianness

Примечание: некоторые пользователи отмечают, что при компиляции в некоторых ситуациях это может привести к неправильному ответу.