Який найшвидший / найефективніший спосіб знайти найвищий встановлений біт (msb) у цілому цілому в C?


119

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

Я знаю, що POSIX підтримує ffs()метод у strings.h для пошуку першого набору бітів, але, здається, не існує відповідного fls()методу.

Чи є якийсь дійсно очевидний спосіб зробити це, чого я пропускаю?

Що робити у випадках, коли ви не можете використовувати POSIX функції для перенесення?

Редагувати: як щодо рішення, яке працює як для 32, так і для 64-бітових архітектур (багато списків кодів здаються, що вони працюють лише на 32-бітових вкладишах).


тут є кілька реалізацій: graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear (Редагувати: Після перечитування свого питання я розумію, що посилання, наведене вище, призначене для пошуку крайнього правого встановленого біта, а не крайнього лівого, як потрібно, хоча без почуття розміру слова, відповісти хитромудрий)
марнуйте


Це рахує нулі праворуч ; питання стосувалося нулів зліва. Принаймні, у швидкому обіймі я цього не бачу.
Дарій Бекон

2
Ви конкретно хочете, щоб бітове число 'n', або 2 ^ n буде достатньо?
Альнітак

1
Подивіться на алгоритми "Журнал бази 2" - як стверджує Андерсон у статті: "База журналів 2 цілого числа є такою ж, як і позиція найвищого набору бітів (або найзначнішого набору бітів, MSB)"
Майкл Берр

Відповіді:


64

GCC має :

 - Вбудована функція: int __builtin_clz (без підпису int x)
     Повертає кількість провідних 0-біт у X, починаючи з максимум
     значна бітова позиція. Якщо X дорівнює 0, результат не визначений.

 - Вбудована функція: int __builtin_clzl (без підпису)
     Подібно до `__builtin_clz ', за винятком типу аргументу` не підписано
     довго'.

 - Вбудована функція: int __builtin_clzll (неподписана довга довга)
     Подібно до `__builtin_clz ', за винятком типу аргументу` не підписано
     довго-довго '.

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


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

Щоб уникнути необхідності цього робити, ваш інший варіант - це особливості платформи, такі як ARM GCC __clz(не потрібен заголовок) або x86 _lzcnt_u32на процесорах, що підтримують lzcntінструкцію. (Остерігайтеся, що lzcntрозшифровується як bsrна старих процесорах, а не помилок, що дає 31-фунт для ненульових входів.)

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

("Не визначений результат" - це не визначена поведінка C, а лише значення, яке не визначено. Це насправді все, що було в реєстрі призначення, коли розпочалася інструкція. AMD це документи, Intel не робить, але процесори Intel реалізують таку поведінку . Але це НЕ то , що раніше було в змінної C ви зіставляють, що це звичайно не як речі працюють , коли GCC перетворює з в асемблер Див. також Чому порушення «вихідна» залежно від LZCNT справи? )


5
У MSVC з'явиться _BitScanReverse
храповик, який

1
Невизначена нульова поведінка дозволяє компілювати їх до однієї інструкції BSR на x86, навіть коли LZCNT недоступний. Це велика перевага для __builtin_ctzover ffs, яка компілюється в BSF та CMOV для обробки випадку "введення-було-нуль". У архітектурах, що не мають достатньо короткої реалізації (наприклад, старий ARM без clzінструкції), gcc надсилає виклик функції помічника libgcc.
Пітер Кордес

41

Припускаючи, що ви перебуваєте на x86 та грі для трохи вбудованого асемблера, Intel пропонує BSRінструкцію ("біт сканування назад"). Це швидко на деяких x86 (мікрокодування на інших). З посібника:

Шукає вихідний операнд для найбільш значущого набору бітів (1 біт). Якщо знайдено найзначніший 1 біт, його бітовий індекс зберігається в операнді призначення. Вихідним операндом може бути регістр або місце пам'яті; операнд призначення - це регістр. Бітовий індекс - це непідписане зміщення від біта 0 вихідного операнда. Якщо операнд джерела вмісту дорівнює 0, вміст операнда призначення не визначений.

(Якщо ви перебуваєте на PowerPC, є аналогічна cntlzінструкція ("рахувати провідні нулі").)

Приклад коду для 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), що він значно швидший, ніж циклічний код.


4
Насправді ця інструкція зазвичай мікрокодується в цикл і досить повільна.
rlbond

2
Який ? BSR чи CNTLZ? Коли я читав посилання x86-timing.pdf, на яке згадувалося вище, BSR лише повільно працює на Pentiums Netburst. Про PowerPC я нічого не знаю.
тайм

5
... Добре, при більш детальному огляді переконайтеся, що "BSR працює лише швидко на P3 / Pentium-M / Core2 x86s". Повільно на Netburst та AMD.
тайм

1
Лише вгору: Ваші два останні посилання мертві.
Baum mit Augen

2
@rlbond: так, BSR на P4 Прескотт становить 2 уп з затримкою 16 циклів (!), один на пропускну здатність 4c. Але на попередньому Netburst це лише 4 циклу затримки (все-таки 2 упп) і один на 2с пропускної здатності. (джерело: agner.org/optimize ). У більшості процесорів він також має залежність від його виходу, на яку gcc не враховується (коли вхід дорівнює нулю, фактична поведінка полягає в тому, щоб залишити призначення незмінним). Це може призвести до таких проблем, як stackoverflow.com/questions/25078285/… . IDK, чому gcc пропустив BSR під час виправлення цього.
Пітер Кордес

38

Оскільки 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 розглядає будь-яке ненульове значення як істинне) і повертає число змін, це має ідеальний сенс. Це також означає, що він працює навіть тоді, коли встановлено більше одного біта - результат завжди є найбільш значущим бітом.

Якщо прокрутити вниз на цій сторінці, є більш швидкі, складніші варіанти. Однак, якщо ви знаєте, що ви маєте справу з числами з великою кількістю ведучих нулів, наївний підхід може забезпечити прийнятну швидкість, оскільки зміщення бітів досить швидке в С, а простий алгоритм не вимагає індексації масиву.

ПРИМІТКА. Використовуючи 64-бітні значення, будьте дуже обережні щодо використання надзвичайно розумних алгоритмів; багато з них коректно працюють лише для 32-бітних значень.


2
@Johan Перехід за допомогою налагоджувача може допомогти пояснити, чому цикл виходить. В основному, це "тому, що вираз у умові оцінюється до 0 (що трактується як помилкове), коли останній 1 біт зміщений праворуч.
Квінн Тейлор

2
Приємна ідея використовувати такий кінцевий результат :)
Йоган

6
Примітка: має бути без підпису, для підписаних цілих чисел правильний зсув не вдається для негативних чисел.
Ксантікс

2
Xantix: Зсув C / C ++ - це логічний зсув, тому він працює чудово. Для Java, JavaScript або D потрібно використовувати оператор логічного зсуву >>>. Плюс, мабуть, компаратор != 0і деяка невказана кількість дужок.
Чейз

8
@Chase: Ні, це не так. Це логічний зсув для непідписаних . Для підписаних це може бути, а може і не бути логічним зрушенням (а це, як правило, арифметична).
Тім Час

17

Це має бути блискавично:

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

25
Пропущено 7 бітових змін, 5 чи інструкцій, мультиплікації та потенційного кешу. :) Ви це орієнтували чи дивились на створений асемблер? Це може закінчитися досить повільно, залежно від того, яку частину компілятора може усунути.
джельф

5
Я тут новий. Хлопці не отримую негативних голосів. Я надав єдину відповідь із вихідним кодом, який насправді працює.
головний герой

9
"Можлива помилка кешу", ймовірно, пов'язана з цим кодом, який вимагає доступу до своєї таблиці пошуку. Якщо ця таблиця не буде кешована, коли вона викликається, під час її отримання буде стояти стійло. Це може зробити гірші показники набагато гіршими, ніж рішення, що не використовують LUT.
розмотувати

13
не насправді сенс. Він використовує набагато більше кешу даних, ніж потрібно (більше ніж один рядок кеша, навіть), і більше кешу інструкцій, ніж потрібно. Ви, ймовірно, отримаєте пропуски кеш-пам'яті, яких можна було б уникнути під час першого виклику функції, і це забруднить кеш більше, ніж потрібно, тому після виклику інший код може зіткнутися з більшою кількістю пропусків, ніж потрібно. LUT часто не вартує клопотів, тому що пропуски кешу дорогі. Але я лише сказав, що я хотів би зробити тестування, перш ніж я стверджував, що це "блискавка". Не те, що це, безумовно, проблема.
jalf

6
У таблиці є 32 записи, і кожне значення <255 (127), тому визначте таблицю як тип безпідписаного знака, і вона вміститься в одному 32-байтовому рядку кешу L1. І вся справа вкладається у дві лінії кеша.
ChuckCottrill

16

Це схоже на те, щоб знайти своєрідний цілий журнал. Існують бітові трюки, але я створив для цього власний інструмент. Мета звичайно - швидкість.

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

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

Ця версія скидає значення в подвійне, а потім зчитує показник, який повідомляє вам, де був біт. Фантастичний зсув і віднімання полягає у витягуванні належних частин зі значення IEEE.

Трохи швидше використовувати поплавці, але поплавок може дати вам лише перші 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 та пізніших версіях використовуйте a union {double d; uint32_t u[2];};. Але зауважте, що в C ++, покарання типу Union підтримується лише на деяких компіляторах як розширення, а не в ISO C ++.


Зазвичай це буде повільніше, ніж особливості платформи для внутрішньої інструкції підрахунку нулів, але портативний ISO C такої функції не має. Деяким процесорам також відсутня інструкція підрахунку нульових значень, але деякі з них можуть ефективно перетворювати цілі числа в double. Введення тексту в біт FP назад до цілого числа може бути повільним (наприклад, для PowerPC він вимагає зберігання / перезавантаження та зазвичай спричиняє стійку при натисканні на зберігання).

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


2
Так. І gcc буде робити неприємні речі з таким кодом з -O2 через оптимізацію типу.
MSN

4
Кастинг між цілим числом і плаваючою крапкою може бути напрочуд дорогим для процесора x86
jalf

1
Так, витрати на ППУ високі. Але фактичні вимірювання часу показали, що це було швидше, ніж всебітні опс або особливо будь-які петлі. Спробуйте і найшвидше прийміть завжди найкращу пораду. У мене не було проблем з GCC і -O2 з цим, хоча.
SPWorley

1
Хіба це не визначена поведінка (читання значення через покажчик несумісного типу)?
dreamlax

3
Захват Хакера пояснює, як виправити помилку в 32-бітових поплавках у 5-3 підрахунку ведучих 0. Ось їх код, який використовує анонімний союз для перекриття asFloat та asInt: k = k & ~ (k >> 1); asFloat = (float) k + 0,5f; n = 158 - (asInt >> 23); (і так, це покладається на певну поведінку поведінку)
D Coetzee

11

Каз Кілхеку тут

Я визначив два підходи для цього над 63 бітовими числами (довгий довгий тип на gcc x86_64), тримаючись подалі від бітового знака.

(Мені здається, мені потрібен цей "знайти найвищий біт" для чогось.)

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

Дерево рішень (найвищий_біт_unroll) орієнтоване на 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-й бітовий набір!

На вході 0x3FFFFFFFFFFFFFFFFF версія дерева рішень є дещо швидшою, ніж на 1, і показує, що вона на 1120% швидша (в 12,2 рази), ніж біт-перемикач.

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


9
Я не кажу, що це непогано, але Ваша програма тестування тут перевіряє лише те саме число, яке після 2-3 ітерацій встановить передбачувачі гілок на остаточне положення і після цього вони зроблять ідеальні передбачення гілок. Хороша річ, що при абсолютно випадковому розподілі половина чисел матиме близький до ідеального прогнозування, а саме біт63.
Surt


6
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


7
Наведений вище код не відповідає на питання. Він повертає непідписане ціле число, де найбільше значення в біті в x залишається увімкненим, а всі інші біти вимикаються. Питання полягало у поверненні позиції найбільш значущих на біт.
головний герой

3
Потім можна використовувати послідовність підходу Де Бруйна, щоб знайти індекс встановленого біта. :-)
R .. GitHub ЗАСТОСУЄТЬСЯ ДО ЛОСУ

5
@Protagonist, він сказав у коментарі, що або достатньо.
rlbond

Цей (з тієї самої сторінки) зробив би те, що вам потрібно, але це потребує додаткової функції. agregate.org/MAGIC/#Log2%20of%20an%20Integer
Квінн Тейлор

1
BSR швидко працює на процесорах Intel, щонайменше з Core2. LZCNT швидкий на процесорних процесорах AMD, і gcc використовує його, __builtin_clzякщо це ввімкнено за допомогою -march=nativeабо чогось іншого (оскільки він швидкий на кожному процесорі, який його підтримує). Навіть на процесорах, таких як AMD Bulldozer-сімейство, де BSR "повільний", це не так повільно: 7 m-ops з 4 циклами затримки і один на 4c пропускної здатності. В Атомі BSR дійсно повільний: 16 циклів. Для Сільвермонта це 10 уп із затримкою 10 циклів. Це може бути дещо нижча затримка, ніж BSR у Silvermont, але IDK.
Пітер Кордес

6

Ось декілька (простих) орієнтирів алгоритмів, наведених на цій сторінці ...

Алгоритми не перевірені на всіх входах неподписаного 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;
}

6

Хоча я, мабуть, використовував би цей метод лише тоді, коли мені абсолютно потрібна найкраща ефективність (наприклад, для написання якихось настільних ігор AI за участю бітбордів), найбільш ефективним рішенням є використання вбудованого ASM. Дивіться розділ «Оптимізація» у цій публікації блогу щодо коду з поясненням.

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

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

Для розширення: стандартне рішення циклу (зміщення вліво та перевірка MSB), мабуть, є найбільш читабельним. Як і у всіх випадках, пов’язаних із біт-подвійністю, швидкість ASM не може бути побита, хоча немає сенсу забивати ваш код, якщо це не потрібно. Хаки - це посереднє рішення - йти так чи інакше.
Нолдорін

Я б сказав, що прийняття логарифму було б ідеально читабельним рішенням (перевірте згенерований ASM, щоб побачити, чи зможе компілятор оптимізувати його для використання цієї інструкції asm)
jalf

Іноді вбудоване рішення ASM відбувається повільніше, в залежності від реалізації мікрокоду CPU.
rlbond

5
@rlbound: Я навряд чи вірю в це, хоча, можливо, помиляюся. На будь-якому сучасному процесорі можна було б подумати, що його перекладуть на одну інструкцію ....
Noldorin

3
@Noldorin трохи пізно, але .. Це за визначенням єдина інструкція, але якщо це мікрокодування, як підказує rlbond, то одна інструкція може розшифрувати внутрішню групу µops. Це, як правило, стосується мікроархітектур AMD та Intel Atom, але для звичайних мікроархітектур Intel це одна операція аж донизу.
Гарольд

4

У мене була потреба в рутинному виконанні цього завдання, і перед тим, як шукати в Інтернеті (і знайти цю сторінку), я придумав власне рішення, засноване на двійковому пошуку. Хоча я впевнений, що хтось робив це раніше! Він працює в постійний час і може бути швидшим, ніж "очевидне" рішення, розміщене, хоча я не висловлюю великих претензій, просто розміщую його для зацікавлення.

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

4

ось якийсь двійковий пошук, він працює з усіма типами (без підпису!) цілих чисел

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

4
Не забудьте використовувати ALL_CAPS для typedefs або взагалі нічого, крім макросів препроцесора. Це широко прийнята конвенція.
підкреслюй_d

4

Тут є кілька надто складних відповідей. Техніка Дебруїна повинна використовуватися лише тоді, коли вхід вже має потужність дві, інакше є кращий спосіб. За потужністю 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;
}

Це має забезпечити найвищу пропускну здатність будь-якого з наведених тут програмних відповідей, але якщо ви дзвоните лише час від часу, віддайте перевагу без табличного рішення, як мій перший фрагмент.


1
Деякі відповіді не мають гілок, але це, ймовірно, буде складено з умовними гілками. Ви лише багаторазово виконували тести з однаковим значенням, чи простий візерунок чи щось таке? Галузеве передбачення є вбивцею для виконання. stackoverflow.com/questions/11227809/…
Пітер Кордес

3

Як зазначено у відповідях вище, існує ряд способів визначення найбільш значущого біта. Однак, як було також зазначено, методи, ймовірно, будуть унікальними або для 32-бітових, або для 64-бітних регістрів. Сторінка bithacks stanford.edu пропонує рішення, які працюють як для 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;
}

Не був int r; спочатку визначено над #ifdef BUILD_64прапором? У такому разі переопределення в умовному випадку не потребуватиме.
Девід К. Ранкін

3

Версія на 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 петлі при використанні "неподписаного int")


Якщо ви пишете це з потрійним оператором ( msb += (n>>msb) ? step : -step;), більше компіляторів, швидше за все, роблять безрозгалужувальну тривогу, уникаючи неправильних прогнозів гілок на кожному кроці ( stackoverflow.com/questions/11227809/… ).
Пітер Кордес

3

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

Більшість рішень (особливо тих, де використовується якась двійкова схема пошуку або наївний підхід, який робить лінійне сканування справа наліво), здається, нехтують тим фактом, що для довільних двійкових чисел не так багато, які починаються з дуже довгої послідовності нулі. Насправді для будь-якої бітової ширини половина всіх цілих чисел починається з 1, а чверть з них починається з 01 . Подивіться, куди я заходжу? Мій аргумент , що лінійне сканування починаючи з наймолодшого біта позиції до найменш значущих (зліва направо) не так «лінійний» , як це може виглядати на перший погляд.

Можна показати 1 , що для будь-якої бітової ширини середня кількість бітів, які потрібно перевірити, становить не більше 2. Це означає амортизовану складність у часі O (1) щодо кількості бітів (!) .

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

Ось "наївний" підхід, який я придумав, який, принаймні, на моїй машині перемагає більшість інших підходів (двійкові схеми пошуку 32-бітових вкладок завжди вимагають журналу 2 (32) = 5 кроків, тоді як цей дурний алгоритм вимагає менше ніж 2 в середньому) - вибачте, що це C ++, а не чистий 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 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 до нескінченності

Таким чином, наивний алгоритм зліва направо має фактично амортизовану постійну часову складність O (1) для будь-якої кількості біт.


2
Я не думаю, що це обов'язково справедливе припущення, що входи до функцій msb мають тенденцію до рівномірного розподілу. На практиці ці входи мають тенденцію бути регістрами переривань або бітбордами або іншою структурою даних з нерівномірно розподіленими значеннями. Щодо справедливого орієнтиру, я вважаю, що безпечніше вважати, що результати (а не входи) будуть розподілені рівномірно.
johnwbyrd

3

дав нам 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потреб, остерігатися , а також, тому що:

-∞ повертається і FE_DIVBYZERO піднімається

Я написав приклад з цією перевіркою , що довільно встановлює Indexдля ULONG_MAXтут: https://ideone.com/u26vsi


The наслідком лише відповіді ефекту 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


Ні, _BitScanReverseповертає 0, лише якщо вхід був 0. Це подібно інструкції x86BSR , яка встановлює ZF на основі лише входу, а не виходу. Цікаво, що MS висловлює документи як такі, що залишаються indexневстановленими, коли не 1знайдено біт; що також відповідає поведінці x86 asm bsr. (AMD документує це як залишаючи реєстр призначення немодифікованим на src = 0, але Intel просто говорить про невизначений вихід, навіть якщо їхні процесори реалізують поведінку, що не змінюється.) Це на відміну від x86 lzcnt, що дає 32для не знайденого.
Пітер Кордес

@PeterCordes _BitScanReverseвикористовує нульову індексацію, таким чином, якщо nце 1, то індекс встановленого біта насправді 0. На жаль, як ви кажете, якщо nце 0, то вихід також 0 :( Це означає, що немає можливості використовувати повернення до розрізняйте nзначення 1 або 0. Це те, що я намагався спілкуватися. Як ви вважаєте, чи є кращий спосіб сказати це?
Джонатан Мі

Я думаю, ти говориш про те, як це встановлюється Index. Це не повернене значення. Він повертає логічне значення, яке не відповідає дійсності, якщо вхід дорівнював нулю (і саме тому Index передається за посиланням, а не повертається нормально). godbolt.org/g/gQKJdE . І я перевірив: незважаючи на формулювання документів MS, _BitScanReverseIndex не залишає ввімкнутим індекс n==0: ви просто отримаєте те значення, яке було в реєстрі. (Який у вашому випадку був, мабуть, тим самим реєстром, який він використовував Indexзгодом, що призводить до того, що ви бачите 0).
Пітер Кордес

Це питання не позначене c ++.
технозавр

@technosaurus Спасибі, я забув себе. З огляду на те, що питання C є у нас, фактично, log2з C99.
Джонатан Мей

2

Подумайте побіжно операторів.

Я неправильно зрозумів це питання вперше. Ви повинні створити int із самим лівим бітовим набором (інші нульові). Якщо припустити, що cmp встановлено на це значення:

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

Що ви маєте на увазі перетворення в рядок? Визначення ffs приймає int і повертає int. Де було б перетворення? А якій меті послужило б перетворення, якщо ми шукаємо шматочки словом?
dreamlax

Я не знав про цю функцію.
Василь

8Повинно бути CHAR_BIT. Це малоймовірно, що це найшвидший спосіб, тому що неправильне передбачення гілки трапиться при виході з циклу, якщо це не використовується з одним і тим же входом повторно. Крім того, для невеликих входів (безлічі нулів), вона повинна багато циклічно. Це як резервний спосіб, який ви використовуєте як просту для перевірки версію в тестовому пристрої для порівняння з оптимізованими версіями.
Пітер Кордес

2

Розширення на еталон Джоша ... можна поліпшити кліз наступним чином

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

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

Щодо asm: зауважте, що є bsr та bsrl (це "довга" версія). нормальна може бути трохи швидшою.


1

Зауважте, що ви намагаєтеся це обчислити ціле число 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);
}

1

Здійснення цього, оскільки це "ще один" підхід, схоже, відрізняється від інших, що вже були надані.

повертається, -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];
}

1

Вау, це було багато відповідей. Мені не шкода відповіді на старе запитання.

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

Ця відповідь досить схожа на іншу відповідь ... ну добре.


Запис суми змін як 1<<kприємний штрих. А як з масками? (1 << (1<<k-1)-1<< (1<<k-1)? ( most optimal? Ви порівнюєте чудовий?)
сіра борода

@greybeard Якщо ви подивитесь на правки цього питання, побачите, коли я додав "оптимальну" частину. Я забув її зняти, коли змінив свою відповідь. Крім того, я не знаю , чому ви говорите про в масках? (Які маски? Я не стежу за вами)
Гаррі Свенссон,

( (бітова) маска - це значення, які використовуються для вибіркового вибору / очищення бітів / використовуваних в &і &~.) Ви можете замінити шістнадцяткові константи на подібні ((type)1<<(1<<k))-1<<(1<<k).
сіра борода

О так, я використовую маски, я про це зовсім забув. Я відповів на це пару місяців тому ... - Гммм, тому що він оцінюється під час компіляції, я кажу, що це еквівалентно шестигранним значенням. Однак одне криптовалютне, а одне - шістнадцяткове.
Гаррі Свенссон

0

Код:

    // 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


ухххх. що? як ця функція? це будь-яким чином портативний?
підкреслюй_d

Коди у вікні портативні. Функція FYL2X () є інструкцією fpu, але може бути перенесена і може знаходитися в деякій бібліотеці FPU / математики.
jemin

@underscore_d Це працює, тому що числа з плаваючою точкою нормалізуються ... перетворення в подвійне зміщення бітів мантіси для усунення провідних нулів, і цей код витягує експонент і коригує його для визначення кількості зміщених бітів. Це, звичайно, не залежить від архітектури, але, ймовірно, буде працювати на будь-якій машині, на яку ви натрапите.
Джим Балтер

Це альтернативна версія цієї відповіді , дивіться там коментарі щодо продуктивності та портативності. ( В зокрема, без портативність покажчика кастингу типу-каламбурів.) Він використовує адресу математику тільки перезавантажувати високі 32 біта double, який, ймовірно , добре , якщо він на самому справі магазин / перезавантаження замість типу-каламбур який - то інший спосіб, наприклад , з такою movqінструкцією, як ви можете потрапити сюди на x86.
Пітер Кордес

Також зверніть увагу на мій [коментар до цієї відповіді], де я пропоную запеклі попередження про те, що цей метод дає неправильну відповідь для значень у (принаймні) діапазоні [7FFFFFFFFFFFFE00 - 7FFFFFFFFFFFFFFF].
Гленн Слейден

0

Інший плакат запропонував таблицю пошуку, використовуючи широкомасштабний пошук. У випадку, якщо ви хочете отримати трохи більшу продуктивність (ціною 32 Кб пам'яті замість всього 256 записів пошуку), тут ви знайдете рішення, використовуючи 15-бітну таблицю пошуку , в C # 7 для .NET .

Цікава частина - ініціалізація таблиці. Оскільки це порівняно невеликий блок, який ми хочемо впродовж всього процесу, я виділяю для цього некеровану пам'ять, використовуючи Marshal.AllocHGlobal. Як бачите, для досягнення максимальної продуктивності весь приклад написано як рідний:

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 кандидат. НайвищийOne_Tab16A 622 496
 2 кандидати. НайвищийOne_Tab16C 628,234
 3 кандидати. НайвищийOne_Tab8A 649,146
 4 кандидати. НайвищийOne_Tab8B 656 847
 5 кандидатів. НайвищийOne_Tab16B 657,147
 6 кандидатів. НайвищийOne_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 (небезпечний) 760,387
13 _test_B.HighestOne8 (небезпечний) 763,904
14 _test_A.HighestOne3 (небезпечний) 766,433
15 _test_A.HighestOne1 (небезпечний) 767,321
16 _test_A.HighestOne4 (небезпечно) 771,702
17 _test_B.HighestOne2 (небезпечний) 772,136
18 _test_B.HighestOne1 (небезпечний) 772,527
19 _test_B.HighestOne3 (небезпечний) 774,140
20 _test_A.HighestOne7 (небезпечний) 774,581
21 _test_B.HighestOne7 (небезпечний) 775 463
22 _test_A.HighestOne2 (небезпечний) 776,865
23 кандидати. НайвищийOne_NoTab 777 698
24 _test_B.HighestOne6 (небезпечний) 779,481
25 _test_A.HighestOne6 (небезпечний) 781,553
26 _test_B.HighestOne4 (небезпечний) 785,504
27 _test_B.HighestOne5 (небезпечний) 789,797
28 _test_A.HighestOne0 (небезпечно) 809,566
29 _test_B.HighestOne0 (небезпечно) 814,990
30 _highest_one_bit.HighestOne 824,345
30 _bitarray_ext.RtlFindMostSignificantBit 894,069
31 кандидат. НайвищийOne_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

Я не можу уявити, що низька продуктивність походить із цих п'яти рядків, тому винні адміністративні / перехідні штрафи повинні бути винні. Я також був здивований, що тестування дійсно віддало перевагу shortтаблицям прямого пошуку 32 КБ (і 64 КБ) (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];
}

Останнє, що я зазначу, це те, що я був дуже шокований тим, що мій метод deBruijn не став кращим. Це метод, який я раніше широко використовував:

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

Існує багато дискусій про те, наскільки вищі та чудові методи deBruijn в цьому питанні питання , і я схильний погодитися. Моя думка полягає в тому, що в той час як і методи deBruijn, і таблиця прямого пошуку (що я виявився найшвидшим) обидва повинні виконати пошук таблиці, і обидва мають дуже мінімальне розгалуження, тільки deBruijn має 64-бітну операцію множення. Я протестував IndexOfMSBтут лише функції - не deBruijn IndexOfLSB- але я очікую, що останній матиме набагато кращі шанси, оскільки у нього стільки менше операцій (див. Вище), і я, ймовірно, продовжуватиму використовувати її для LSB.


1
Кеш L1D на сучасних процесорах x86 становить лише 32 КБ. Великий LUT, ймовірно, буде гіршим, ніж малий LUT, якщо ви не використовуєте одні й ті самі значення неодноразово. Якщо ви цього не зробите, у вас будуть часті пропуски кешу.
Пітер Кордес

0

Мій скромний метод дуже простий:

MSB (x) = INT [Журнал (x) / Журнал (2)]

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

Це можна легко та швидко адаптувати до будь-якої мови програмування. Спробуйте на своєму калькуляторі, щоб переконатися, що він працює.


Це працює, якщо все, що вас цікавить, - це ефективність розробника. Якщо ви хочете ефективність виконання, вам потрібен альтернативний алгоритм.
Мікко Ранталайнен

Це може не вдатися через помилку округлення. Наприклад, у CPython 2 та 3, int(math.log((1 << 48) - 1) / math.log(2))це 48.
benrg

0

Ось швидке рішення для C, яке працює в GCC та Clang ; готовий до копіювання та вклеювання.

#include <limits.h>

unsigned int fls(const unsigned int value)
{
    return (unsigned int)1 << ((sizeof(unsigned int) * CHAR_BIT) - __builtin_clz(value) - 1);
}

unsigned long flsl(const unsigned long value)
{
    return (unsigned long)1 << ((sizeof(unsigned long) * CHAR_BIT) - __builtin_clzl(value) - 1);
}

unsigned long long flsll(const unsigned long long value)
{
    return (unsigned long long)1 << ((sizeof(unsigned long long) * CHAR_BIT) - __builtin_clzll(value) - 1);
}

І трохи вдосконалена версія для C ++ .

#include <climits>

constexpr unsigned int fls(const unsigned int value)
{
    return (unsigned int)1 << ((sizeof(unsigned int) * CHAR_BIT) - __builtin_clz(value) - 1);
}

constexpr unsigned long fls(const unsigned long value)
{
    return (unsigned long)1 << ((sizeof(unsigned long) * CHAR_BIT) - __builtin_clzl(value) - 1);
}

constexpr unsigned long long fls(const unsigned long long value)
{
    return (unsigned long long)1 << ((sizeof(unsigned long long) * CHAR_BIT) - __builtin_clzll(value) - 1);
}

Код припускає, що valueцього не буде 0. Якщо ви хочете дозволити 0, вам потрібно змінити його.


0

Я припускаю, що ваше запитання стосується цілого числа (називається 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 & 0x80000000) && 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). Будь ласка, дайте мені знати, якщо я щось забув. Я не перевіряв його, але він повинен працювати чудово.


v <<= 1це невизначена поведінка (UB), коли v < 0.
chux

0x8000000, можливо, ви маєте на увазі додаткові 0.
ММ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.