Каков самый быстрый/самый эффективный способ найти самый старший бит набора (msb) в целых числах в C?

Если у меня есть целое число n, и я хочу знать позицию самого значимого бита (то есть, если младший значащий бит справа, я хочу знать позицию самого дальнего левого бита, который является 1), каков самый быстрый/наиболее эффективный метод поиска?

Я знаю, что POSIX поддерживает метод ffs() в strings.h, чтобы найти первый бит набора, но, похоже, не существует соответствующего метода fls().

Есть ли какой-то действительно очевидный способ сделать это, что мне не хватает?

Как насчет случаев, когда вы не можете использовать функции POSIX для переносимости?

Изменить: как насчет решения, которое работает как на 32-битных, так и на 64-битных архитектурах (многие из списков кода выглядят так, как будто они работают только на 32-битных ints).

Ответ 1

GCC имеет:

 -- Built-in Function: int __builtin_clz (unsigned int x)
     Returns the number of leading 0-bits in X, starting at the most
     significant bit position.  If X is 0, the result is undefined.

 -- Built-in Function: int __builtin_clzl (unsigned long)
     Similar to '__builtin_clz', except the argument type is 'unsigned
     long'.

 -- Built-in Function: int __builtin_clzll (unsigned long long)
     Similar to '__builtin_clz', except the argument type is 'unsigned
     long long'.

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


Полезный трюк, если ваш вход может быть нулевым, это __builtin_clz(x | 1): безусловная установка младшего бита без изменения каких-либо других значений приводит к выводу 0 для x=0, без изменения выхода для любого другого входа.

Чтобы избежать необходимости делать это, ваш другой вариант - это встроенные функции для конкретной платформы, такие как ARM GCC __clz (заголовок не нужен) или x86 _lzcnt_u32 на процессорах, которые поддерживают инструкцию lzcnt. (Помните, что lzcnt декодирует как bsr на старых процессорах вместо сбоя, что дает 31-lzcnt для ненулевых входов.)

К сожалению, нет способа использовать преимущества различных команд CLZ на платформах, отличных от x86, которые определяют результат для input = 0 как 32 или 64 (в зависимости от ширины операнда). x86 lzcnt делает то же самое, в то время как bsr создает битовый индекс, который компилятор должен переворачивать, если вы не используете 31-__builtin_clz(x).

("Неопределенный результат" - это не неопределенное поведение C, а просто значение, которое не определено. Это фактически то, что было в регистре назначения, когда выполнялась инструкция. AMD документирует это, Intel не делает, но процессоры Intel действительно реализуют такое поведение Но это не то, что было ранее в переменной C, которую вы присваиваете, это обычно не так, как все работает, когда gcc превращает C. в asm. См. Также Почему нарушение "выходной зависимости" LZCNT имеет значение?)

Ответ 2

Предполагая, что вы на x86 и игра для небольшого встроенного ассемблера, Intel предоставляет инструкцию BSR ("бит сканирования назад"). Он быстро работает на некоторых x86 (микрокодированных на других). Из руководства:

Ищет исходный операнд для наиболее значимого бита набора (1 бит). Если найден самый значительный 1 бит, его битовый индекс сохраняется в операнде-адресате. Исходным операндом может быть регистр или ячейка памяти; операнд назначения является регистром. Битовый индекс - это беззнаковое смещение от бит 0 исходного операнда. Если операндом источника контента является 0, содержимое операнда назначения не определено.

(Если вы находитесь на PowerPC, то есть cntlz ("count leading zeros").)

Пример кода для gcc:

#include <iostream>

int main (int,char**)
{
  int n=1;
  for (;;++n) {
    int msb;
    asm("bsrl %1,%0" : "=r"(msb) : "r"(n));
    std::cout << n << " : " << msb << std::endl;
  }
  return 0;
}

См. Также этот встроенный учебник ассемблера, который показывает (раздел 9.4), что он значительно быстрее, чем код цикла.

Ответ 3

Так как 2 ^ N является целым числом с только множеством N-го бита (1 < N), то поиск позиции (N) наивысшего бита набора является целым лог-основанием 2 этого целого числа.

http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious

unsigned int v;
unsigned r = 0;

while (v >>= 1) {
    r++;
}

Этот "очевидный" алгоритм может быть не прозрачным для всех, но когда вы понимаете, что код сдвигается вправо на один бит до тех пор, пока левый бит не будет смещен (обратите внимание, что C обрабатывает любое ненулевое значение как истинное) и возвращает количество смен, это имеет смысл. Это также означает, что он работает, даже если установлено более одного бита - результат всегда для самого значимого бита.

Если вы прокрутите страницу вниз, есть более быстрые и более сложные варианты. Однако, если вы знаете, что имеете дело с числами с большим количеством нулей, наивный подход может обеспечить приемлемую скорость, так как смещение битов довольно быстро на C, а простой алгоритм не требует индексации массива.

ПРИМЕЧАНИЕ.. При использовании 64-битных значений будьте предельно осторожны в использовании экстра-умных алгоритмов; многие из них работают корректно только для 32-битных значений.

Ответ 4

Это должно быть молниеносно:

int msb(unsigned int v) {
  static const int pos[32] = {0, 1, 28, 2, 29, 14, 24, 3,
    30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19,
    16, 7, 26, 12, 18, 6, 11, 5, 10, 9};
  v |= v >> 1;
  v |= v >> 2;
  v |= v >> 4;
  v |= v >> 8;
  v |= v >> 16;
  v = (v >> 1) + 1;
  return pos[(v * 0x077CB531UL) >> 27];
}

Ответ 5

Это похоже на поиск целочисленного журнала. Есть бит-трюки, но я сделал свой собственный инструмент для этого. Целью курса является скорость.

Моя реализация заключается в том, что у CPU есть автоматический бит-детектор уже, используемый для целочисленного преобразования с плавающей точкой! Так что используйте это.

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

Эта версия передает значение в double, затем считывает показатель экспоненты, который сообщает вам, где был бит. Фантастический сдвиг и вычитание - это извлечение правильных частей из значения IEEE.

Это немного быстрее, чем использование float, но поплавок может дать вам первые 24-битные позиции из-за его меньшей точности.


Чтобы сделать это безопасно, без неопределенного поведения в C++ или C, используйте memcpy вместо кастования указателя для ввода типа. Компиляторы знают, как эффективно внедрять его.

// static_assert(sizeof(double) == 2 * sizeof(uint32_t), "double isn't 8-byte IEEE binary64");
// and also static_assert something about FLT_ENDIAN?

double ff=(double)(v|1);

uint32_t tmp;
memcpy(&tmp, ((const char*)&ff)+sizeof(uint32_t), sizeof(uint32_t));
return (tmp>>20)-1023;

Или в C99 и более поздних версиях, используйте union {double d; uint32_t u[2];}; union {double d; uint32_t u[2];}; , Но обратите внимание, что в C++ использование типа Punning поддерживается только некоторыми компиляторами в качестве расширения, а не ISO C++.


Обычно это будет медленнее, чем специфичная для платформы функция для команды подсчета ведущего нуля, но переносимый ISO C не имеет такой функции. Некоторым процессорам также не хватает команды подсчета нулевого значения, но некоторые из них могут эффективно преобразовывать целые числа в double. Тип-punning битового паттерна FP обратно в целое число может быть медленным (например, на PowerPC требуется сохранение/перезагрузка и обычно вызывает сбой при загрузке).

Этот алгоритм потенциально может быть полезен для реализаций SIMD, поскольку меньшее количество процессоров имеет SIMD lzcnt. x86 получил только такую инструкцию с AVX512CD

Ответ 6

Kaz Kylheku здесь

Я сравнил два подхода для этого более 63-битных чисел (длинный длинный тип на gcc x86_64), держась подальше от знакового бита.

(Мне нужно, чтобы это "находило наивысший бит" для чего-то, вы видите.)

Я реализовал двоичный поиск, основанный на данных (тесно основанный на одном из приведенных выше ответов). Я также реализовал полностью развернутое дерево решений вручную, которое представляет собой просто код с непосредственными операндами. Нет циклов, нет таблиц.

Дерево решений (high_bit_unrolled) оценивается на 69% быстрее, за исключением случая n = 0, для которого двоичный поиск имеет явный тест.

Специальный тест для двоичного поиска для случая 0 только на 48% быстрее, чем дерево решений, которое не имеет специального теста.

Компилятор, машина: (GCC 4.5.2, -O3, x86-64, 2867 МГц Intel Core i5).

int highest_bit_unrolled(long long n)
{
  if (n & 0x7FFFFFFF00000000) {
    if (n & 0x7FFF000000000000) {
      if (n & 0x7F00000000000000) {
        if (n & 0x7000000000000000) {
          if (n & 0x4000000000000000)
            return 63;
          else
            return (n & 0x2000000000000000) ? 62 : 61;
        } else {
          if (n & 0x0C00000000000000)
            return (n & 0x0800000000000000) ? 60 : 59;
          else
            return (n & 0x0200000000000000) ? 58 : 57;
        }
      } else {
        if (n & 0x00F0000000000000) {
          if (n & 0x00C0000000000000)
            return (n & 0x0080000000000000) ? 56 : 55;
          else
            return (n & 0x0020000000000000) ? 54 : 53;
        } else {
          if (n & 0x000C000000000000)
            return (n & 0x0008000000000000) ? 52 : 51;
          else
            return (n & 0x0002000000000000) ? 50 : 49;
        }
      }
    } else {
      if (n & 0x0000FF0000000000) {
        if (n & 0x0000F00000000000) {
          if (n & 0x0000C00000000000)
            return (n & 0x0000800000000000) ? 48 : 47;
          else
            return (n & 0x0000200000000000) ? 46 : 45;
        } else {
          if (n & 0x00000C0000000000)
            return (n & 0x0000080000000000) ? 44 : 43;
          else
            return (n & 0x0000020000000000) ? 42 : 41;
        }
      } else {
        if (n & 0x000000F000000000) {
          if (n & 0x000000C000000000)
            return (n & 0x0000008000000000) ? 40 : 39;
          else
            return (n & 0x0000002000000000) ? 38 : 37;
        } else {
          if (n & 0x0000000C00000000)
            return (n & 0x0000000800000000) ? 36 : 35;
          else
            return (n & 0x0000000200000000) ? 34 : 33;
        }
      }
    }
  } else {
    if (n & 0x00000000FFFF0000) {
      if (n & 0x00000000FF000000) {
        if (n & 0x00000000F0000000) {
          if (n & 0x00000000C0000000)
            return (n & 0x0000000080000000) ? 32 : 31;
          else
            return (n & 0x0000000020000000) ? 30 : 29;
        } else {
          if (n & 0x000000000C000000)
            return (n & 0x0000000008000000) ? 28 : 27;
          else
            return (n & 0x0000000002000000) ? 26 : 25;
        }
      } else {
        if (n & 0x0000000000F00000) {
          if (n & 0x0000000000C00000)
            return (n & 0x0000000000800000) ? 24 : 23;
          else
            return (n & 0x0000000000200000) ? 22 : 21;
        } else {
          if (n & 0x00000000000C0000)
            return (n & 0x0000000000080000) ? 20 : 19;
          else
            return (n & 0x0000000000020000) ? 18 : 17;
        }
      }
    } else {
      if (n & 0x000000000000FF00) {
        if (n & 0x000000000000F000) {
          if (n & 0x000000000000C000)
            return (n & 0x0000000000008000) ? 16 : 15;
          else
            return (n & 0x0000000000002000) ? 14 : 13;
        } else {
          if (n & 0x0000000000000C00)
            return (n & 0x0000000000000800) ? 12 : 11;
          else
            return (n & 0x0000000000000200) ? 10 : 9;
        }
      } else {
        if (n & 0x00000000000000F0) {
          if (n & 0x00000000000000C0)
            return (n & 0x0000000000000080) ? 8 : 7;
          else
            return (n & 0x0000000000000020) ? 6 : 5;
        } else {
          if (n & 0x000000000000000C)
            return (n & 0x0000000000000008) ? 4 : 3;
          else
            return (n & 0x0000000000000002) ? 2 : (n ? 1 : 0);
        }
      }
    }
  }
}

int highest_bit(long long n)
{
  const long long mask[] = {
    0x000000007FFFFFFF,
    0x000000000000FFFF,
    0x00000000000000FF,
    0x000000000000000F,
    0x0000000000000003,
    0x0000000000000001
  };
  int hi = 64;
  int lo = 0;
  int i = 0;

  if (n == 0)
    return 0;

  for (i = 0; i < sizeof mask / sizeof mask[0]; i++) {
    int mi = lo + (hi - lo) / 2;

    if ((n >> mi) != 0)
      lo = mi;
    else if ((n & (mask[i] << lo)) != 0)
      hi = mi;
  }

  return lo + 1;
}

Быстрая и грязная тестовая программа:

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

int highest_bit_unrolled(long long n);
int highest_bit(long long n);

main(int argc, char **argv)
{
  long long n = strtoull(argv[1], NULL, 0);
  int b1, b2;
  long i;
  clock_t start = clock(), mid, end;

  for (i = 0; i < 1000000000; i++)
    b1 = highest_bit_unrolled(n);

  mid = clock();

  for (i = 0; i < 1000000000; i++)
    b2 = highest_bit(n);

  end = clock();

  printf("highest bit of 0x%llx/%lld = %d, %d\n", n, n, b1, b2);

  printf("time1 = %d\n", (int) (mid - start));
  printf("time2 = %d\n", (int) (end - mid));
  return 0;
}

Используя только -O2, разница становится больше. Дерево решений почти в четыре раза быстрее.

Я также сравнивал с наивным битовым кодом:

int highest_bit_shift(long long n)
{
  int i = 0;
  for (; n; n >>= 1, i++)
    ; /* empty */
  return i;
}

Это происходит только для небольших чисел, как и следовало ожидать. При определении того, что старший бит равен 1 для n == 1, он сравнивается более чем на 80% быстрее. Однако половина случайно выбранных чисел в 63-битном пространстве имеет 63-й бит!

На входе 0x3FFFFFFFFFFFFFFF версия дерева решений довольно немного быстрее, чем на 1, и показывает, что она на 1120% быстрее (12,2 раза), чем бит-сдвиг.

Я также проведу сравнение дерева решений с встроенными контроллерами GCC, а также попробую смесь входов, а не повторяю их против того же числа. Возможно, произойдет некоторое предсказание ветвящихся ветвей и, возможно, некоторые нереалистичные сценарии кеширования, которые делают его искусственно быстрее при повторениях.

Ответ 7

Что насчет

int highest_bit(unsigned int a) {
    int count;
    std::frexp(a, &count);
    return count - 1;
}

?

Ответ 8

Хотя я, вероятно, использовал бы этот метод только в том случае, если бы мне абсолютно требовалась максимальная производительность (например, для написания искусственного интеллекта настольных игр с использованием битбордов), наиболее эффективным решением является использование встроенного ASM. См. Раздел "Оптимизации" этого блога, где приведен код с пояснениями

[...] bsrl ассемблера bsrl вычисляет позицию bsrl бита. Таким образом, мы могли бы использовать это утверждение asm:

asm ("bsrl %1, %0" 
     : "=r" (position) 
     : "r" (number));

Ответ 9

unsigned int
msb32(register unsigned int x)
{
        x |= (x >> 1);
        x |= (x >> 2);
        x |= (x >> 4);
        x |= (x >> 8);
        x |= (x >> 16);
        return(x & ~(x >> 1));
}

1 регистр, 13 инструкций. Верьте или нет, это обычно быстрее, чем упомянутая выше инструкция BSR, которая работает в линейном времени. Это логарифмическое время.

От http://aggregate.org/MAGIC/#Most%20Significant%201%20Bit

Ответ 10

Вот некоторые (простые) эталонные алгоритмы, которые в настоящее время заданы на этой странице...

Алгоритмы не тестировались на все входы без знака int; поэтому сначала проверьте это, прежде чем слепо использовать что-то;)

На моей машине clz (__builtin_clz) и asm работают лучше всего. asm кажется еще быстрее, чем clz... но это может быть связано с простым эталоном...

//////// go.c ///////////////////////////////
// compile with:  gcc go.c -o go -lm
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/***************** math ********************/

#define POS_OF_HIGHESTBITmath(a) /* 0th position is the Least-Signif-Bit */    \
  ((unsigned) log2(a))         /* thus: do not use if a <= 0 */  

#define NUM_OF_HIGHESTBITmath(a) ((a)               \
                  ? (1U << POS_OF_HIGHESTBITmath(a))    \
                  : 0)



/***************** clz ********************/

unsigned NUM_BITS_U = ((sizeof(unsigned) << 3) - 1);
#define POS_OF_HIGHESTBITclz(a) (NUM_BITS_U - __builtin_clz(a)) /* only works for a != 0 */

#define NUM_OF_HIGHESTBITclz(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITclz(a))  \
                 : 0)


/***************** i2f ********************/

double FF;
#define POS_OF_HIGHESTBITi2f(a) (FF = (double)(ui|1), ((*(1+(unsigned*)&FF))>>20)-1023)


#define NUM_OF_HIGHESTBITi2f(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITi2f(a))  \
                 : 0)




/***************** asm ********************/

unsigned OUT;
#define POS_OF_HIGHESTBITasm(a) (({asm("bsrl %1,%0" : "=r"(OUT) : "r"(a));}), OUT)

#define NUM_OF_HIGHESTBITasm(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITasm(a))  \
                 : 0)




/***************** bitshift1 ********************/

#define NUM_OF_HIGHESTBITbitshift1(a) (({   \
  OUT = a;                  \
  OUT |= (OUT >> 1);                \
  OUT |= (OUT >> 2);                \
  OUT |= (OUT >> 4);                \
  OUT |= (OUT >> 8);                \
  OUT |= (OUT >> 16);               \
      }), (OUT & ~(OUT >> 1)))          \



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

#define POS_OF_HIGHESTBITbitshift2(a) (({   \
  OUT = a;                  \
  OUT |= OUT >> 1;              \
  OUT |= OUT >> 2;              \
  OUT |= OUT >> 4;              \
  OUT |= OUT >> 8;              \
  OUT |= OUT >> 16;             \
  OUT = (OUT >> 1) + 1;             \
      }), POS[(OUT * 0x077CB531UL) >> 27])

#define NUM_OF_HIGHESTBITbitshift2(a) ((a)              \
                       ? (1U << POS_OF_HIGHESTBITbitshift2(a)) \
                       : 0)



#define LOOPS 100000000U

int main()
{
  time_t start, end;
  unsigned ui;
  unsigned n;

  /********* Checking the first few unsigned values (you'll need to check all if you want to use an algorithm here) **************/
  printf("math\n");
  for (ui = 0U; ui < 18; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITmath(ui));

  printf("\n\n");

  printf("clz\n");
  for (ui = 0U; ui < 18U; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITclz(ui));

  printf("\n\n");

  printf("i2f\n");
  for (ui = 0U; ui < 18U; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITi2f(ui));

  printf("\n\n");

  printf("asm\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITasm(ui));
  }

  printf("\n\n");

  printf("bitshift1\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift1(ui));
  }

  printf("\n\n");

  printf("bitshift2\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift2(ui));
  }

  printf("\n\nPlease wait...\n\n");


  /************************* Simple clock() benchmark ******************/
  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITmath(ui);
  end = clock();
  printf("math:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITclz(ui);
  end = clock();
  printf("clz:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITi2f(ui);
  end = clock();
  printf("i2f:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITasm(ui);
  end = clock();
  printf("asm:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITbitshift1(ui);
  end = clock();
  printf("bitshift1:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITbitshift2(ui);
  end = clock();
  printf("bitshift2\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  printf("\nThe lower, the better. Take note that a negative exponent is good! ;)\n");

  return EXIT_SUCCESS;
}

Ответ 11

Мне нужна была процедура, чтобы сделать это, и, прежде чем искать в Интернете (и найти эту страницу), я придумал собственное решение, основанное на двоичном поиске. Хотя я уверен, что кто-то сделал это раньше! Он работает в постоянное время и может быть быстрее, чем "очевидное" решение опубликовано, хотя я не делаю никаких больших претензий, просто отправляю его на интерес.

int highest_bit(unsigned int a) {
  static const unsigned int maskv[] = { 0xffff, 0xff, 0xf, 0x3, 0x1 };
  const unsigned int *mask = maskv;
  int l, h;

  if (a == 0) return -1;

  l = 0;
  h = 32;

  do {
    int m = l + (h - l) / 2;

    if ((a >> m) != 0) l = m;
    else if ((a & (*mask << l)) != 0) h = m;

    mask++;
  } while (l < h - 1);

  return l;
}

Ответ 12

это какой-то бинарный поиск, он работает со всеми типами (unsigned!) integer

#include <climits>
#define UINT (unsigned int)
#define UINT_BIT (CHAR_BIT*sizeof(UINT))

int msb(UINT x)
{
    if(0 == x)
        return -1;

    int c = 0;

    for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
    if(static_cast<UINT>(x >> i))
    {
        x >>= i;
        c |= i;
    }

    return c;
}

сделать полный:

#include <climits>
#define UINT unsigned int
#define UINT_BIT (CHAR_BIT*sizeof(UINT))

int lsb(UINT x)
{
    if(0 == x)
        return -1;

    int c = UINT_BIT-1;

    for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
    if(static_cast<UINT>(x << i))
    {
        x <<= i;
        c ^= i;
    }

    return c;
}

Ответ 13

Некоторые чрезмерно сложные ответы здесь. Техника Дебруина должна использоваться только тогда, когда вход уже имеет силу два, иначе лучше. Для мощности 2 входа Debruin является самым быстрым, даже быстрее, чем _BitScanReverse на любом тестируемом процессоре. Однако в общем случае _BitScanReverse (или независимо от того, что внутреннее имя вызывается в вашем компиляторе) является самым быстрым (на определенном процессоре он может быть микрокодированным, хотя).

Если внутренняя функция не является опцией, вот оптимальное программное решение для обработки общих входов.

u8  inline log2 (u32 val)  {
    u8  k = 0;
    if (val > 0x0000FFFFu) { val >>= 16; k  = 16; }
    if (val > 0x000000FFu) { val >>= 8;  k |= 8;  }
    if (val > 0x0000000Fu) { val >>= 4;  k |= 4;  }
    if (val > 0x00000003u) { val >>= 2;  k |= 2;  }
    k |= (val & 2) >> 1;
    return k;
}

Обратите внимание, что эта версия не требует поиска Debruin в конце, в отличие от большинства других ответов. Он вычисляет позицию на месте.

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

u8 kTableLog2[256] = {
0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};

u8 log2_table(u32 val)  {
    u8  k = 0;
    if (val > 0x0000FFFFuL) { val >>= 16; k  = 16; }
    if (val > 0x000000FFuL) { val >>=  8; k |=  8; }
    k |= kTableLog2[val]; // precompute the Log2 of the low byte

    return k;
}

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

Ответ 14

Как видно из вышеприведенных ответов, существует несколько способов определения наиболее значимого бита. Однако, как также было указано, методы, вероятно, будут уникальными для 32-битных или 64-битных регистров. Страница stanford.edu bithacks предоставляет решения, которые работают как для 32-битного, так и для 64-битного вычислений. С небольшой работой они могут быть объединены, чтобы обеспечить надежный кросс-архитектурный подход к получению MSB. Решение, к которому я пришел, скомпилировал/обработал 64-разрядные и 32-разрядные компьютеры:

#if defined(__LP64__) || defined(_LP64)
# define BUILD_64   1
#endif

#include <stdio.h>
#include <stdint.h>  /* for uint32_t */

/* CHAR_BIT  (or include limits.h) */
#ifndef CHAR_BIT
#define CHAR_BIT  8
#endif  /* CHAR_BIT */

/* 
 * Find the log base 2 of an integer with the MSB N set in O(N)
 * operations. (on 64bit & 32bit architectures)
 */
int
getmsb (uint32_t word)
{
    int r = 0;
    if (word < 1)
        return 0;
#ifdef BUILD_64
    union { uint32_t u[2]; double d; } t;  // temp
    t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] = 0x43300000;
    t.u[__FLOAT_WORD_ORDER!=LITTLE_ENDIAN] = word;
    t.d -= 4503599627370496.0;
    r = (t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] >> 20) - 0x3FF;
#else
    while (word >>= 1)
    {
        r++;
    }
#endif  /* BUILD_64 */
    return r;
}

Ответ 15

Версия в C с использованием последовательного приближения:

unsigned int getMsb(unsigned int n)
{
  unsigned int msb  = sizeof(n) * 4;
  unsigned int step = msb;
  while (step > 1)
 {
    step /=2;
    if (n>>msb)
     msb += step;
   else
     msb -= step;
 }
  if (n>>msb)
    msb++;
  return (msb - 1);
}

Преимущество: время работы постоянное, независимо от предоставленного числа, поскольку количество циклов всегда одинаковое. (4 цикла при использовании "unsigned int" )

Ответ 16

дал нам log2. Это устраняет необходимость в всех специальных log2 вы видите на этой странице. Вы можете использовать стандартную реализацию log2 следующим образом:

const auto n = 13UL;
const auto Index = (unsigned long)log2(n);

printf("MSB is: %u\n", Index); // Prints 3 (zero offset)

n из 0UL необходимо также 0UL, потому что:

-∞ и FE_DIVBYZERO поднят

Я написал пример с этой проверкой, который произвольно устанавливает Index в ULONG_MAX здесь: https://ideone.com/u26vsi


Следствие для ответа на эфемерный gcc:

const auto n = 13UL;
unsigned long Index;

_BitScanReverse(&Index, n);
printf("MSB is: %u\n", Index); // Prints 3 (zero offset)

В документации для _BitScanReverse указано, что Index:

Загружена битная позиция первого набора бит (1) найдена

На практике я обнаружил, что если n равно 0UL, значение Index равно 0UL, как и для n из 1UL. Но единственное, что гарантировано в документации в случае n из 0UL состоит в том, что возврат:

0, если не было установлено никаких битов

Таким образом, аналогично предпочтительной реализации log2 над возвратом следует проверить параметр Index на значение с флагом в этом случае. Я снова написал пример использования ULONG_MAX для этого значения флага здесь: http://rextester.com/GCU61409

Ответ 17

Рассмотрим побитовые операторы.

Я пропустил этот вопрос в первый раз. Вы должны создать int с самым последним битом (остальные равны нулю). Предполагая, что cmp установлено на это значение:

position = sizeof(int)*8
while(!(n & cmp)){ 
   n <<=1;
   position--;
}

Ответ 18

Расширение теста Josh... можно улучшить clz следующим образом

/***************** clz2 ********************/

#define NUM_OF_HIGHESTBITclz2(a) ((a)                              \
                  ? (((1U) << (sizeof(unsigned)*8-1)) >> __builtin_clz(a)) \
                  : 0)

Что касается asm: обратите внимание, что есть bsr и bsrl (это "длинная" версия). нормальный может быть немного быстрее.

Ответ 19

Вводя это, так как это "еще один" подход, кажется, отличается от других, которые уже были даны.

возвращает -1, если x==0, в противном случае floor( log2(x)) (максимальный результат 31)

Уменьшите с 32 до 4 бит проблемы, затем используйте таблицу. Возможно, неэлегантный, но прагматичный.

Это то, что я использую, когда я не хочу использовать __builtin_clz из-за проблем с переносимостью.

Чтобы сделать его более компактным, вместо него можно использовать цикл для уменьшения, каждый раз добавляя 4 к r, максимально 7 итераций. Или какой-то гибрид, например (для 64 бит): цикл для уменьшения до 8, тест для уменьшения до 4.

int log2floor( unsigned x ){
   static const signed char wtab[16] = {-1,0,1,1, 2,2,2,2, 3,3,3,3,3,3,3,3};
   int r = 0;
   unsigned xk = x >> 16;
   if( xk != 0 ){
       r = 16;
       x = xk;
   }
   // x is 0 .. 0xFFFF
   xk = x >> 8;
   if( xk != 0){
       r += 8;
       x = xk;
   }
   // x is 0 .. 0xFF
   xk = x >> 4;
   if( xk != 0){
       r += 4;
       x = xk;
   }
   // now x is 0..15; x=0 only if originally zero.
   return r + wtab[x];
}

Ответ 20

Обратите внимание, что вы пытаетесь вычислить целое число log2 целого числа,

#include <stdio.h>
#include <stdlib.h>

unsigned int
Log2(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1; int k=0;
    for( step = 1; step < bits; ) {
        n |= (n >> step);
        step *= 2; ++k;
    }
    //printf("%ld %ld\n",x, (x - (n >> 1)) );
    return(x - (n >> 1));
}

Обратите внимание, что вы можете попытаться выполнить поиск более 1 бит за раз.

unsigned int
Log2_a(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1;
    int step2 = 0;
    //observe that you can move 8 bits at a time, and there is a pattern...
    //if( x>1<<step2+8 ) { step2+=8;
        //if( x>1<<step2+8 ) { step2+=8;
            //if( x>1<<step2+8 ) { step2+=8;
            //}
        //}
    //}
    for( step2=0; x>1L<<step2+8; ) {
        step2+=8;
    }
    //printf("step2 %d\n",step2);
    for( step = 0; x>1L<<(step+step2); ) {
        step+=1;
        //printf("step %d\n",step+step2);
    }
    printf("log2(%ld) %d\n",x,step+step2);
    return(step+step2);
}

В этом подходе используется двоичный поиск

unsigned int
Log2_b(unsigned long x)
{
    unsigned long n = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int hbit = bits-1;
    unsigned int lbit = 0;
    unsigned long guess = bits/2;
    int found = 0;

    while ( hbit-lbit>1 ) {
        //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        //when value between guess..lbit
        if( (x<=(1L<<guess)) ) {
           //printf("%ld < 1<<%d %ld\n",x,guess,1L<<guess);
            hbit=guess;
            guess=(hbit+lbit)/2;
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        }
        //when value between hbit..guess
        //else
        if( (x>(1L<<guess)) ) {
            //printf("%ld > 1<<%d %ld\n",x,guess,1L<<guess);
            lbit=guess;
            guess=(hbit+lbit)/2;
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        }
    }
    if( (x>(1L<<guess)) ) ++guess;
    printf("log2(x%ld)=r%d\n",x,guess);
    return(guess);
}

Другой метод двоичного поиска, возможно, более читаемый,

unsigned int
Log2_c(unsigned long x)
{
    unsigned long v = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int step = bits;
    unsigned int res = 0;
    for( step = bits/2; step>0; )
    {
        //printf("log2(%ld) v %d >> step %d = %ld\n",x,v,step,v>>step);
        while ( v>>step ) {
            v>>=step;
            res+=step;
            //printf("log2(%ld) step %d res %d v>>step %ld\n",x,step,res,v);
        }
        step /= 2;
    }
    if( (x>(1L<<res)) ) ++res;
    printf("log2(x%ld)=r%ld\n",x,res);
    return(res);
}

И потому, что вы захотите проверить их,

int main()
{
    unsigned long int x = 3;
    for( x=2; x<1000000000; x*=2 ) {
        //printf("x %ld, x+1 %ld, log2(x+1) %d\n",x,x+1,Log2(x+1));
        printf("x %ld, x+1 %ld, log2_a(x+1) %d\n",x,x+1,Log2_a(x+1));
        printf("x %ld, x+1 %ld, log2_b(x+1) %d\n",x,x+1,Log2_b(x+1));
        printf("x %ld, x+1 %ld, log2_c(x+1) %d\n",x,x+1,Log2_c(x+1));
    }
    return(0);
}

Ответ 21

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

Большинство решений (особенно тех, которые используют какую-то двоичную схему поиска или наивный подход, который выполняет линейное сканирование справа налево), по-видимому, игнорируют тот факт, что для произвольных двоичных чисел не так много, которые начинаются с очень длинная последовательность нулей. Фактически, для любой битовой ширины половина всех целых чисел начинается с 1, а четверть из них начинается с 01. Посмотрите, к чему я иду? Мой аргумент состоит в том, что линейное сканирование, начиная с наиболее значимой позиции бит до наименее значимого (слева направо), не так "линейно", как может показаться на первый взгляд.

Можно показать 1 что для любой битовой ширины среднее число бит, которое необходимо протестировать, не превышает 2. Это переводится в усредненную временную сложность O (1) относительно количества бит (!).

Конечно, худший случай по-прежнему O (n), хуже, чем O (log (n)), который вы получаете с помощью двоично-поисковых подходов, но так как существует так мало худших случаев, они незначительны для большинства приложений ( Обновить: не совсем: их может быть немного, но они могут произойти с высокой вероятностью - см. Обновление ниже).

Вот "наивный" подход, который я придумал, который по крайней мере на моей машине превосходит большинство других подходов (для двоичных поисковых схем для 32-битных ints всегда требуется log 2 (32) = 5 шагов, тогда как этот глупый алгоритм требует в среднем меньше 2) - извините, что это С++, а не чистый C:

template <typename T>
auto msb(T n) -> int
{
    static_assert(std::is_integral<T>::value && !std::is_signed<T>::value,
        "msb<T>(): T must be an unsigned integral type.");

    for (T i = std::numeric_limits<T>::digits - 1, mask = 1 << i; i >= 0; --i, mask >>= 1)
    {
        if ((n & mask) != 0)
            return i;
    }

    return 0;
}

Обновить. Хотя то, что я написал здесь, совершенно верно для любых целых чисел, где каждая комбинация бит одинаково вероятна (мой тест скорости просто измерял, сколько времени потребовалось, чтобы определить MSB для всех 32- битные целые числа), целые числа в реальной жизни, для которых такая функция будет вызываться, обычно следуют по другому шаблону: например, в моем коде эта функция используется для определения того, является ли размер объекта равным 2 или найти следующая мощность 2 больше или равна размеру объекта. Я предполагаю, что большинство приложений, использующих MSB, включают числа, которые намного меньше максимального числа, которое может представлять целое число (размеры объектов редко используют все биты в size_t). В этом случае мое решение действительно будет работать хуже, чем подход с двоичным поиском, поэтому последнее, вероятно, должно быть предпочтительным, хотя мое решение будет быстрее проходить через все целые числа.
TL; DR:. Целые числа в реальной жизни, вероятно, будут иметь предвзятость в худшем случае этого простого алгоритма, что в конечном итоге сделает его хуже, несмотря на то, что он амортизируется O ( 1) для действительно произвольных целых чисел.

1 Аргумент идет следующим образом (черновик): Пусть n - количество бит (бит-ширина). Всего существует целых чисел 2 n, которые могут быть представлены битами n. Существуют целые числа 2 n - 1, начинающиеся с 1 (первая 1, оставшаяся n - 1 бит может быть любым). Эти целые числа требуют только одного взаимодействия цикла для определения MSB. Кроме того, существуют целые числа 2 n - 2, начинающиеся с 01, требующие 2 итераций, 2 n - 3, начиная с 001, требуя 3 итерации и т.д.

Если суммировать все требуемые итерации для всех возможных целых чисел и разделить их на 2 n, общее число целых чисел, мы получим среднее число итераций, необходимых для определения MSB для n -битных целых чисел:

(1 * 2 n - 1 + 2 * 2 n - 2 + 3 * 2 n - 3 +... + n)/2 n

Эта серия средних итераций фактически сходится и имеет предел 2 для n к бесконечности p >

Таким образом, наивный алгоритм слева направо имеет фактически амортизированную постоянную временную сложность O (1) для любого количества бит.

Ответ 22

Жаль, это было много ответов. Я не жалею, что ответил на старый вопрос.

int result = 0;//could be a char or int8_t instead
if(value){//this assumes the value is 64bit
    if(0xFFFFFFFF00000000&value){  value>>=(1<<5); result|=(1<<5);  }//if it is 32bit then remove this line
    if(0x00000000FFFF0000&value){  value>>=(1<<4); result|=(1<<4);  }//and remove the 32msb
    if(0x000000000000FF00&value){  value>>=(1<<3); result|=(1<<3);  }
    if(0x00000000000000F0&value){  value>>=(1<<2); result|=(1<<2);  }
    if(0x000000000000000C&value){  value>>=(1<<1); result|=(1<<1);  }
    if(0x0000000000000002&value){  result|=(1<<0);  }
}else{
  result=-1;
}

Этот ответ довольно похож на другой ответ... ну ладно.

Ответ 23

Мой скромный метод очень прост:

MSB (x) = INT [Log (x)/Log (2)]

Перевод: MSB x - это целочисленное значение (Log of Base x, деленное на Log of Base 2).

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

Ответ 24

Код:

    // x>=1;
    unsigned func(unsigned x) {
    double d = x ;
    int p= (*reinterpret_cast<long long*>(&d) >> 52) - 1023;
    printf( "The left-most non zero bit of %d is bit %d\n", x, p);
    }

Или получить целую часть инструкции FPU FYL2X (Y * Log2 X), установив Y = 1

Ответ 25

Другой плакат предоставил подстановки-таблицу с использованием байтами шириной поиска. В случае, если вы хотите немного повысить производительность (за счет 32K памяти вместо 256 записей поиска), вот решение, использующее 15-битную таблицу поиска, в С# 7 для .NET.

Интересная часть - инициализация таблицы. Поскольку это относительно небольшой блок, который мы хотим для всего жизненного цикла процесса, я Marshal.AllocHGlobal для него неуправляемую память, используя Marshal.AllocHGlobal. Как вы можете видеть, для максимальной производительности весь пример записывается как native:

readonly static byte[] msb_tab_15;

// Initialize a table of 32768 bytes with the bit position (counting from LSB=0)
// of the highest 'set' (non-zero) bit of its corresponding 16-bit index value.
// The table is compressed by half, so use (value >> 1) for indexing.
static MyStaticInit()
{
    var p = new byte[0x8000];

    for (byte n = 0; n < 16; n++)
        for (int c = (1 << n) >> 1, i = 0; i < c; i++)
            p[c + i] = n;

    msb_tab_15 = p;
}

Таблица требует одноразовой инициализации с помощью приведенного выше кода. Он доступен только для чтения, поэтому для одновременного доступа может использоваться общая глобальная копия. С помощью этой таблицы вы можете быстро найти целочисленный журнал 2, который мы ищем здесь, для всех различных целых ширин (8, 16, 32 и 64 бит).

Обратите внимание, что запись таблицы для 0, единственное целое число, для которого понятие "старший бит набора" не определено, присваивается значение -1. Это различие необходимо для правильной обработки 0-значных верхних слов в приведенном ниже коде. Без дальнейших церемоний, вот код для каждого из различных целых примитивов:

ulong (64-разрядная версия)

/// <summary> Index of the highest set bit in 'v', or -1 for value '0' </summary>
public static int HighestOne(this ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 0x40) - 1;      // handles cases v==0 and MSB==63

    int j = /**/ (int)((0xFFFFFFFFU - v /****/) >> 58) & 0x20;
    j |= /*****/ (int)((0x0000FFFFU - (v >> j)) >> 59) & 0x10;
    return j + msb_tab_15[v >> (j + 1)];
}

uint (32-разрядная версия)

/// <summary> Index of the highest set bit in 'v', or -1 for value '0' </summary>
public static int HighestOne(uint v)
{
    if ((int)v <= 0)
        return (int)((v >> 26) & 0x20) - 1;     // handles cases v==0 and MSB==31

    int j = (int)((0x0000FFFFU - v) >> 27) & 0x10;
    return j + msb_tab_15[v >> (j + 1)];
}

Различные перегрузки для вышеуказанных

public static int HighestOne(long v) => HighestOne((ulong)v);
public static int HighestOne(int v) => HighestOne((uint)v);
public static int HighestOne(ushort v) => msb_tab_15[v >> 1];
public static int HighestOne(short v) => msb_tab_15[(ushort)v >> 1];
public static int HighestOne(char ch) => msb_tab_15[ch >> 1];
public static int HighestOne(sbyte v) => msb_tab_15[(byte)v >> 1];
public static int HighestOne(byte v) => msb_tab_15[v >> 1];

Это полное, работающее решение, которое представляет лучшую производительность на.NET 4.7.2 для многочисленных альтернатив, которые я сравнивал со специализированным жгутом тестирования производительности. Некоторые из них упомянуты ниже. Параметры теста были равномерной плотностью всех 65-битных позиций, то есть 0... 31/63 плюс значение 0 (что дает результат -1). Биты ниже позиции целевого индекса заполнялись случайным образом. Тесты были только для x64, режим выпуска, при включенной оптимизации JIT.




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


Версия, приведенная выше, была закодирована, поскольку Tab16A был последовательным победителем на многих прогонах. Эти различные кандидаты в активной рабочей/скретч-форме можно найти здесь, здесь и здесь.

 1  candidates.HighestOne_Tab16A               622,496
 2  candidates.HighestOne_Tab16C               628,234
 3  candidates.HighestOne_Tab8A                649,146
 4  candidates.HighestOne_Tab8B                656,847
 5  candidates.HighestOne_Tab16B               657,147
 6  candidates.HighestOne_Tab16D               659,650
 7  _highest_one_bit_UNMANAGED.HighestOne_U    702,900
 8  de_Bruijn.IndexOfMSB                       709,672
 9  _old_2.HighestOne_Old2                     715,810
10  _test_A.HighestOne8                        757,188
11  _old_1.HighestOne_Old1                     757,925
12  _test_A.HighestOne5  (unsafe)              760,387
13  _test_B.HighestOne8  (unsafe)              763,904
14  _test_A.HighestOne3  (unsafe)              766,433
15  _test_A.HighestOne1  (unsafe)              767,321
16  _test_A.HighestOne4  (unsafe)              771,702
17  _test_B.HighestOne2  (unsafe)              772,136
18  _test_B.HighestOne1  (unsafe)              772,527
19  _test_B.HighestOne3  (unsafe)              774,140
20  _test_A.HighestOne7  (unsafe)              774,581
21  _test_B.HighestOne7  (unsafe)              775,463
22  _test_A.HighestOne2  (unsafe)              776,865
23  candidates.HighestOne_NoTab                777,698
24  _test_B.HighestOne6  (unsafe)              779,481
25  _test_A.HighestOne6  (unsafe)              781,553
26  _test_B.HighestOne4  (unsafe)              785,504
27  _test_B.HighestOne5  (unsafe)              789,797
28  _test_A.HighestOne0  (unsafe)              809,566
29  _test_B.HighestOne0  (unsafe)              814,990
30  _highest_one_bit.HighestOne                824,345
30  _bitarray_ext.RtlFindMostSignificantBit    894,069
31  candidates.HighestOne_Naive                898,865

Примечательно, что ужасная производительность ntdll.dll!RtlFindMostSignificantBit через P/Invoke:

[DllImport("ntdll.dll"), SuppressUnmanagedCodeSecurity, SecuritySafeCritical]
public static extern int RtlFindMostSignificantBit(ulong ul);

Это действительно слишком плохо, потому что здесь вся действительная функция:

    RtlFindMostSignificantBit:
        bsr rdx, rcx  
        mov eax,0FFFFFFFFh  
        movzx ecx, dl  
        cmovne      eax,ecx  
        ret

Я не могу представить, как плохая производительность возникает из-за этих пяти строк, поэтому обвинения в управляемых/нативных переходах должны быть виноваты. Я также был удивлен тем, что тестирование действительно благоприятствовало 32KB (и 64KB) short (16-разрядным) таблицам прямого поиска по 128-байтным (и 256-байтовым) byte (8-битным) таблицам поиска. Я думал, что следующее будет более конкурентоспособным с 16-битным поиском, но последнее последовательно превзошло это:

public static int HighestOne_Tab8A(ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 64) - 1;

    int j;
    j =  /**/ (int)((0xFFFFFFFFU - v) >> 58) & 32;
    j += /**/ (int)((0x0000FFFFU - (v >> j)) >> 59) & 16;
    j += /**/ (int)((0x000000FFU - (v >> j)) >> 60) & 8;
    return j + msb_tab_8[v >> j];
}

Последнее, что я укажу, это то, что я был очень шокирован тем, что мой метод деБрайна не стал лучше. Это метод, который я ранее использовал повсеместно:

const ulong N_bsf64 = 0x07EDD5E59A4E28C2,
            N_bsr64 = 0x03F79D71B4CB0A89;

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

public static int IndexOfLSB(ulong v) =>
    v != 0 ? bsf64[((v & (ulong)-(long)v) * N_bsf64) >> 58] : -1;

public static int IndexOfMSB(ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 64) - 1;

    v |= v >> 1; v |= v >> 2;  v |= v >> 4;   // does anybody know a better
    v |= v >> 8; v |= v >> 16; v |= v >> 32;  // way than these 12 ops?
    return bsr64[(v * N_bsr64) >> 58];
}

Там много дискуссий о том, как превосходные и отличные методы деБрайна в этом вопросе SO, и я, как правило, соглашался. Мое предположение заключается в том, что, хотя и методы deBruijn и direct lookup table (которые, как мне показалось, были самыми быстрыми), должны выполнять поиск в таблице, и оба имеют очень минимальное разветвление, только дебрюин имеет 64-битную операцию умножения. Я тестировал здесь IndexOfMSB функции IndexOfMSB не индекс DeBruijn IndexOfLSB --but. Я ожидаю, что последний будет иметь гораздо больше шансов, так как он имеет намного меньше операций (см. Выше), и я, скорее всего, продолжу использовать его для LSB.

Ответ 26

Я предполагаю, что ваш вопрос касается целого числа (называемого v ниже), а не целого числа без знака.

int v = 612635685; // whatever value you wish

unsigned int get_msb(int v)
{
    int r = 31;                         // maximum number of iteration until integer has been totally left shifted out, considering that first bit is index 0. Also we could use (sizeof(int)) << 3 - 1 instead of 31 to make it work on any platform.

    while (!(v & 0x8000000) && r--) {   // mask of the highest bit
        v <<= 1;                        // multiply integer by 2.
    }
    return r;                           // will even return -1 if no bit was set, allowing error catch
}

Если вы хотите, чтобы он работал без учета знака, вы можете добавить дополнительный "v << = 1;" перед циклом (и изменить значение r до 30 соответственно). Пожалуйста, дайте мне знать, если я что-то забыл. Я не тестировал его, но он должен работать нормально.

Ответ 27

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

Вот код:

Funct() { 
  int number; int count;

  while(number > 0) {
    number = number << 1;
    count++;
  }

  printf("It is the no "%d" bit from the left", (count+1));    
}