Положение младшего значащего бита, которое установлено

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

Тривиальная реализация такова:

unsigned GetLowestBitPos(unsigned value)
{
   assert(value != 0); // handled separately

   unsigned pos = 0;
   while (!(value & 1))
   {
      value >>= 1;
      ++pos;
   }
   return pos;
}

Есть идеи, как выжать из него несколько циклов?

(Примечание: этот вопрос предназначен для людей, которые пользуются такими вещами, а не для людей, чтобы сказать мне, что xyzoptimization - это зло.)

[edit] Спасибо всем за идеи! Я узнал еще кое-что. Круто!

Ответ 1

Bit Twiddling Hacks предлагает отличную коллекцию бит-тиддлинг-хаков с обсуждением производительности и оптимизации. Мое любимое решение для вашей проблемы (с этого сайта) "умножение и поиск":

unsigned int v;  // find the number of trailing zeros in 32-bit v 
int r;           // result goes here
static const int MultiplyDeBruijnBitPosition[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
};
r = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x077CB531U)) >> 27];

Полезные ссылки:

Ответ 2

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

ffs (3) - справочная страница Linux

Имя

ffs - найти первый бит, установленный словом

Сводка

#include <strings.h>
int ffs(int i);
#define _GNU_SOURCE
#include <string.h>
int ffsl(long int i);
int ffsll(long long int i);

Описание

Функция ffs() возвращает положение первого (наименее значимого) бита, установленного в слове i. Меньшим значащим разрядом является позиция 1 и наиболее значимое положение, например. 32 или 64. Функции ffsll() и ffsl() делают то же самое, но принимают аргументы разного размера.

Возвращаемое значение

Эти функции возвращают позицию первого битового набора или 0, если в я не установлены биты.

Соответствует

4.3BSD, POSIX.1-2001.

Примечания

BSD-системы имеют прототип в <string.h>.

Ответ 3

Существует инструкция сборки x86 (bsf), которая сделает это.:)

Больше оптимизировано?!

Боковое примечание:

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

Ответ 4

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

Если у вас есть одна инструкция этого класса, вы можете дешево подражать другим.

Потратьте минутку, чтобы работать с ним на бумаге, и понимаете, что x & (x-1) очистит младший бит набора в x, а ( x & ~(x-1) ) вернет только самый младший бит набора, независимо от архитектуры, длины слова и т.д. Зная это, тривиально использовать аппаратный счетчик-ведущий-нуль/наивысший набор бит, чтобы найти младший бит набора, если нет явной инструкции для этого.

Если не существует соответствующей аппаратной поддержки вообще, многократно реализующая реализация count-leading-zeroes, заданная здесь, или одна из те, которые находятся на странице Bit Twiddling Hacks, можно тривиально преобразовать, чтобы дать наименьший бит набора, используя указанные выше идентификаторы, и имеет то преимущество, что он не имеет полномочий.

Ответ 5

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

Ваша таблица (256 8-разрядных записей) должна содержать индекс LSB для каждого номера в диапазоне 0-255. Вы проверяете каждый байт своего значения и находите самый низкий ненулевой байт, затем используйте это значение для поиска реального индекса.

Для этого требуется 256-байтовая память, но если скорость этой функции настолько важна, что ее 256-байты стоит того,

например.

byte lowestBitTable[256] = {
.... // left as an exercise for the reader to generate
};

unsigned GetLowestBitPos(unsigned value)
{
  // note that order to check indices will depend whether you are on a big 
  // or little endian machine. This is for little-endian
  byte* bytes = (byte*)value;
  if (bytes[0])
    return lowestBitTable[bytes[0]];
  else if (bytes[1])
      return lowestBitTable[bytes[1]] + 8;
  else if (bytes[2])
      return lowestBitTable[bytes[2]] + 16;
  else
      return lowestBitTable[bytes[3]] + 24;  
}

Ответ 6

Weee, множество решений, а не контрольный показатель. Вам, должно быть, стыдно за себя, -)

Моя машина - Intel i530 (2,9 ГГц), работающая под управлением Windows 7 64-бит. Я скомпилирован с 32-разрядной версией MinGW.

$ gcc --version
gcc.exe (GCC) 4.7.2

$ gcc bench.c -o bench.exe -std=c99 -Wall -O2
$ bench
Naive loop.         Time = 2.91  (Original questioner)
De Bruijn multiply. Time = 1.16  (Tykhyy)
Lookup table.       Time = 0.36  (Andrew Grant)
FFS instruction.    Time = 0.90  (ephemient)
Branch free mask.   Time = 3.48  (Dan / Jim Balter)
Double hack.        Time = 3.41  (DocMax)

$ gcc bench.c -o bench.exe -std=c99 -Wall -O2 -march=native
$ bench
Naive loop.         Time = 2.92
De Bruijn multiply. Time = 0.47
Lookup table.       Time = 0.35
FFS instruction.    Time = 0.68
Branch free mask.   Time = 3.49
Double hack.        Time = 0.92

Мой код:

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


#define ARRAY_SIZE 65536
#define NUM_ITERS 5000  // Number of times to process array


int find_first_bits_naive_loop(unsigned nums[ARRAY_SIZE])
{
    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            unsigned value = nums[i];
            if (value == 0)
                continue;
            unsigned pos = 0;
            while (!(value & 1))
            {
                value >>= 1;
                ++pos;
            }
            total += pos + 1;
        }
    }

    return total;
}


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

    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            unsigned int c = nums[i];
            total += MultiplyDeBruijnBitPosition[((unsigned)((c & -c) * 0x077CB531U)) >> 27];
        }
    }

    return total;
}


unsigned char lowestBitTable[256];
int get_lowest_set_bit(unsigned num) {
    unsigned mask = 1;
    for (int cnt = 1; cnt <= 32; cnt++, mask <<= 1) {
        if (num & mask) {
            return cnt;
        }
    }

    return 0;
}
int find_first_bits_lookup_table(unsigned nums[ARRAY_SIZE])
{
    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            unsigned int value = nums[i];
            // note that order to check indices will depend whether you are on a big 
            // or little endian machine. This is for little-endian
            unsigned char *bytes = (unsigned char *)&value;
            if (bytes[0])
                total += lowestBitTable[bytes[0]];
            else if (bytes[1])
              total += lowestBitTable[bytes[1]] + 8;
            else if (bytes[2])
              total += lowestBitTable[bytes[2]] + 16;
            else
              total += lowestBitTable[bytes[3]] + 24;
        }
    }

    return total;
}


int find_first_bits_ffs_instruction(unsigned nums[ARRAY_SIZE])
{
    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            total +=  __builtin_ffs(nums[i]);
        }
    }

    return total;
}


int find_first_bits_branch_free_mask(unsigned nums[ARRAY_SIZE])
{
    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            unsigned value = nums[i];
            int i16 = !(value & 0xffff) << 4;
            value >>= i16;

            int i8 = !(value & 0xff) << 3;
            value >>= i8;

            int i4 = !(value & 0xf) << 2;
            value >>= i4;

            int i2 = !(value & 0x3) << 1;
            value >>= i2;

            int i1 = !(value & 0x1);

            int i0 = (value >> i1) & 1? 0 : -32;

            total += i16 + i8 + i4 + i2 + i1 + i0 + 1;
        }
    }

    return total;
}


int find_first_bits_double_hack(unsigned nums[ARRAY_SIZE])
{
    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            unsigned value = nums[i];
            double d = value ^ (value - !!value); 
            total += (((int*)&d)[1]>>20)-1022; 
        }
    }

    return total;
}


int main() {
    unsigned nums[ARRAY_SIZE];
    for (int i = 0; i < ARRAY_SIZE; i++) {
        nums[i] = rand() + (rand() << 15);
    }

    for (int i = 0; i < 256; i++) {
        lowestBitTable[i] = get_lowest_set_bit(i);
    }


    clock_t start_time, end_time;
    int result;

    start_time = clock();
    result = find_first_bits_naive_loop(nums);
    end_time = clock();
    printf("Naive loop.         Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);

    start_time = clock();
    result = find_first_bits_de_bruijn(nums);
    end_time = clock();
    printf("De Bruijn multiply. Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);

    start_time = clock();
    result = find_first_bits_lookup_table(nums);
    end_time = clock();
    printf("Lookup table.       Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);

    start_time = clock();
    result = find_first_bits_ffs_instruction(nums);
    end_time = clock();
    printf("FFS instruction.    Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);

    start_time = clock();
    result = find_first_bits_branch_free_mask(nums);
    end_time = clock();
    printf("Branch free mask.   Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);

    start_time = clock();
    result = find_first_bits_double_hack(nums);
    end_time = clock();
    printf("Double hack.        Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);
}

Ответ 7

У OMG это просто спирально.

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

В любое время, когда у вас есть ветка, CPU должен угадать, какая ветка будет занята. Трубка инструкции загружается инструкциями, которые ведут вниз по угаданному пути. Если ЦП догадался неправильно, тогда заголовок команды загорается, а другая ветвь должна быть загружена.

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

Количество потерянных циклов процессора сильно варьируется от одного типа процессора к другому. Но вы можете ожидать от 20 до 150 потерянных циклов процессора.

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

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

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

Мои извинения, если это уже было рассмотрено.

Каждый компилятор, который я использую, кроме XCODE AFAIK, имеет встроенные средства компилятора как для прямого бита, так и для обратных битов. Они будут скомпилированы для одной команды сборки на большинстве аппаратных средств без Miss Cache Miss Missing Prediction и других программистов, генерирующих блоки преткновения.

Для компиляторов Microsoft используйте _BitScanForward и _BitScanReverse.
Для GCC используйте __builtin_ffs, __builtin_clz, __builtin_ctz.

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

Извините, я полностью забыл предоставить решение.. Это код, который я использую в IPAD, который не имеет инструкции на уровне сборки для задачи:

unsigned BitScanLow_BranchFree(unsigned value)
{
    bool bwl = (value & 0x0000ffff) == 0;
    unsigned I1 = (bwl * 15);
    value = (value >> I1) & 0x0000ffff;

    bool bbl = (value & 0x00ff00ff) == 0;
    unsigned I2 = (bbl * 7);
    value = (value >> I2) & 0x00ff00ff;

    bool bnl = (value & 0x0f0f0f0f) == 0;
    unsigned I3 = (bnl * 3);
    value = (value >> I3) & 0x0f0f0f0f;

    bool bsl = (value & 0x33333333) == 0;
    unsigned I4 = (bsl * 1);
    value = (value >> I4) & 0x33333333;

    unsigned result = value + I1 + I2 + I3 + I4 - 1;

    return result;
}

Дело в том, чтобы понять, что это не сравнение, которое дорого, но ветвь, которая происходит после сравнения. Сравнение в этом случае вынуждается значением 0 или 1 с символом.. == 0, и результат используется для объединения математики, которая произошла бы по обе стороны от ветки.

Edit:

Вышеприведенный код полностью сломан. Этот код работает и остается без ветвей (если оптимизирован):

int BitScanLow_BranchFree(ui value)
{
    int i16 = !(value & 0xffff) << 4;
    value >>= i16;

    int i8 = !(value & 0xff) << 3;
    value >>= i8;

    int i4 = !(value & 0xf) << 2;
    value >>= i4;

    int i2 = !(value & 0x3) << 1;
    value >>= i2;

    int i1 = !(value & 0x1);

    int i0 = (value >> i1) & 1? 0 : -32;

    return i16 + i8 + i4 + i2 + i1 + i0;
}

Это возвращает -1, если задано 0. Если вам не все равно 0 или счастливы получить 31 для 0, удалите вычисление i0, сохранив кусок времени.

Ответ 8

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

unsigned GetLowestBitPos(unsigned value)
{
   double d = value ^ (value - !!value); 
   return (((int*)&d)[1]>>20)-1023; 
}

Плюсы:

  • нет циклов
  • отсутствие ветвления
  • работает в постоянное время
  • обрабатывает value = 0, возвращая результат в противном случае вне пределов
  • только две строки кода

Минусы:

  • предполагает малое ограничение по кодировке (может быть исправлено путем изменения констант)
  • предполагает, что double является реальным * 8 IEEE float (IEEE 754)

Update: Как отмечается в комментариях, объединение является более чистой реализацией (по крайней мере для C) и будет выглядеть так:

unsigned GetLowestBitPos(unsigned value)
{
    union {
        int i[2];
        double d;
    } temp = { .d = value ^ (value - !!value) };
    return (temp.i[1] >> 20) - 1023;
}

Предполагается, что 32-разрядные ints с малоприводным хранилищем для всех (подумайте о процессорах x86).

Ответ 9

Это можно сделать с наихудшим случаем менее 32 операций:

Принцип: Проверка на 2 или более бит так же эффективна, как проверка на 1 бит.

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

Итак...
если вы проверите 2 бита за раз, когда у вас есть в худшем случае (Nbits/2) + 1 проверит общее количество.
если вы проверите 3 бита за раз, когда у вас есть в худшем случае (Nbits/3) + 2 чека. ...

Оптимальным было бы проверять группы из 4. Что потребует в худшем случае 11 операций вместо вашего 32.

Лучше всего от ваших алгоритмов 1 проверяйте хотя бы на 2 проверки, если вы используете эту идею группировки. Но эта дополнительная 1 проверка в лучшем случае стоит того, чтобы сэкономить наихудший случай.

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

int getLowestBitPos(unsigned int value)
{
    //Group 1: Bits 0-3
    if(value&0xf)
    {
        if(value&0x1)
            return 0;
        else if(value&0x2)
            return 1;
        else if(value&0x4)
            return 2;
        else
            return 3;
    }

    //Group 2: Bits 4-7
    if(value&0xf0)
    {
        if(value&0x10)
            return 4;
        else if(value&0x20)
            return 5;
        else if(value&0x40)
            return 6;
        else
            return 7;
    }

    //Group 3: Bits 8-11
    if(value&0xf00)
    {
        if(value&0x100)
            return 8;
        else if(value&0x200)
            return 9;
        else if(value&0x400)
            return 10;
        else
            return 11;
    }

    //Group 4: Bits 12-15
    if(value&0xf000)
    {
        if(value&0x1000)
            return 12;
        else if(value&0x2000)
            return 13;
        else if(value&0x4000)
            return 14;
        else
            return 15;
    }

    //Group 5: Bits 16-19
    if(value&0xf0000)
    {
        if(value&0x10000)
            return 16;
        else if(value&0x20000)
            return 17;
        else if(value&0x40000)
            return 18;
        else
            return 19;
    }

    //Group 6: Bits 20-23
    if(value&0xf00000)
    {
        if(value&0x100000)
            return 20;
        else if(value&0x200000)
            return 21;
        else if(value&0x400000)
            return 22;
        else
            return 23;
    }

    //Group 7: Bits 24-27
    if(value&0xf000000)
    {
        if(value&0x1000000)
            return 24;
        else if(value&0x2000000)
            return 25;
        else if(value&0x4000000)
            return 26;
        else
            return 27;
    }

    //Group 8: Bits 28-31
    if(value&0xf0000000)
    {
        if(value&0x10000000)
            return 28;
        else if(value&0x20000000)
            return 29;
        else if(value&0x40000000)
            return 30;
        else
            return 31;
    }

    return -1;
}

Ответ 10

Почему бы не использовать двоичный поиск? Это всегда будет завершено после 5 операций (при условии, что размер int равен 4 байтам):

if (0x0000FFFF & value) {
    if (0x000000FF & value) {
        if (0x0000000F & value) {
            if (0x00000003 & value) {
                if (0x00000001 & value) {
                    return 1;
                } else {
                    return 2;
                }
            } else {
                if (0x0000004 & value) {
                    return 3;
                } else {
                    return 4;
                }
            }
        } else { ...
    } else { ...
} else { ...

Ответ 11

Другой метод (разделение по модулю и поиск) заслуживает особого упоминания здесь из той же предоставленной @anton-tykhyy. этот метод очень похож по производительности на метод умножения и поиска DeBruijn с небольшим, но важным различием.

разделение по модулю и поиск

 unsigned int v;  // find the number of trailing zeros in v
    int r;           // put the result in r
    static const int Mod37BitPosition[] = // map a bit value mod 37 to its position
    {
      32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30, 28, 11, 0, 13, 4,
      7, 17, 0, 25, 22, 31, 15, 29, 10, 12, 6, 0, 21, 14, 9, 5,
      20, 8, 19, 18
    };
    r = Mod37BitPosition[(-v & v) % 37];

модуль модуляции и метод поиска возвращают разные значения для v = 0x00000000 и v = FFFFFFFF, тогда как метод умножения и поиска DeBruijn возвращает ноль на обоих входах.

Тест: -

unsigned int n1=0x00000000, n2=0xFFFFFFFF;

MultiplyDeBruijnBitPosition[((unsigned int )((n1 & -n1) * 0x077CB531U)) >> 27]); /* returns 0 */
MultiplyDeBruijnBitPosition[((unsigned int )((n2 & -n2) * 0x077CB531U)) >> 27]); /* returns 0 */
Mod37BitPosition[(((-(n1) & (n1))) % 37)]); /* returns 32 */
Mod37BitPosition[(((-(n2) & (n2))) % 37)]); /* returns 0 */

Ответ 12

В соответствии с страницами ChessScan Chess Programming и мои собственные измерения, вычитание и xor быстрее, чем negate и mask.

(Обратите внимание, что если вы собираетесь подсчитывать завершающие нули в 0, метод, который у меня есть, возвращает 63, тогда как отрицание и маска возвращают 0.)

Ниже приведено 64-битное вычитание и xor:

unsigned long v;  // find the number of trailing zeros in 64-bit v 
int r;            // result goes here
static const int MultiplyDeBruijnBitPosition[64] = 
{
  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
};
r = MultiplyDeBruijnBitPosition[((uint32_t)((v ^ (v-1)) * 0x03F79D71B4CB0A89U)) >> 58];

Для справки, здесь приведена 64-разрядная версия метода отрицания и маски:

unsigned long v;  // find the number of trailing zeros in 64-bit v 
int r;            // result goes here
static const int MultiplyDeBruijnBitPosition[64] = 
{
  0, 1, 48, 2, 57, 49, 28, 3, 61, 58, 50, 42, 38, 29, 17, 4,
  62, 55, 59, 36, 53, 51, 43, 22, 45, 39, 33, 30, 24, 18, 12, 5,
  63, 47, 56, 27, 60, 41, 37, 16, 54, 35, 52, 21, 44, 32, 23, 11,
  46, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6
};
r = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x03F79D71B4CB0A89U)) >> 58];

Ответ 13

Вы можете проверить, установлен ли какой-либо бит младшего порядка. Если это так, посмотрите на нижний порядок оставшихся бит.. Например,

32bit int - проверьте, установлен ли какой-либо из первых 16. Если да, проверьте, установлен ли какой-либо из первых 8. если да, то....

Если нет, проверьте, установлен ли какой-либо из верхних 16.

По существу, это двоичный поиск.

Ответ 14

unsigned GetLowestBitPos(unsigned value)
{
    if (value & 1) return 1;
    if (value & 2) return 2;
    if (value & 4) return 3;
    if (value & 8) return 4;
    if (value & 16) return 5;
    if (value & 32) return 6;
    if (value & 64) return 7;
    if (value & 128) return 8;
    if (value & 256) return 9;
    if (value & 512) return 10;
    if (value & 1024) return 11;
    if (value & 2048) return 12;
    if (value & 4096) return 13;
    if (value & 8192) return 14;
    if (value & 16384) return 15;
    if (value & 32768) return 16;
    if (value & 65536) return 17;
    if (value & 131072) return 18;
    if (value & 262144) return 19;
    if (value & 524288) return 20;
    if (value & 1048576) return 21;
    if (value & 2097152) return 22;
    if (value & 4194304) return 23;
    if (value & 8388608) return 24;
    if (value & 16777216) return 25;
    if (value & 33554432) return 26;
    if (value & 67108864) return 27;
    if (value & 134217728) return 28;
    if (value & 268435456) return 29;
    if (value & 536870912) return 30;
    return 31;
}

50% всех номеров будут возвращены в первой строке кода.

75% всех номеров вернутся к первым двум строкам кода.

87% всех чисел вернутся в первые 3 строки кода.

94% всех чисел будут возвращаться в первых 4 строках кода.

97% всех номеров вернутся в первых 5 строках кода.

и др.

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

Ответ 15

См. мой ответ здесь, как это сделать с помощью одной инструкции x86, за исключением того, что для поиска наименее значимого бита набора вам понадобится BSF ( "бит сканирования вперед" ) вместо BSR.

Ответ 16

Еще одно решение, а не самое быстрое, но похоже на хорошее.
По крайней мере, у нее нет ветвей.;)

uint32 x = ...;  // 0x00000001  0x0405a0c0  0x00602000
x |= x <<  1;    // 0x00000003  0x0c0fe1c0  0x00e06000
x |= x <<  2;    // 0x0000000f  0x3c3fe7c0  0x03e1e000
x |= x <<  4;    // 0x000000ff  0xffffffc0  0x3fffe000
x |= x <<  8;    // 0x0000ffff  0xffffffc0  0xffffe000
x |= x << 16;    // 0xffffffff  0xffffffc0  0xffffe000

// now x is filled with '1' from the least significant '1' to bit 31

x = ~x;          // 0x00000000  0x0000003f  0x00001fff

// now we have 1 below the original least significant 1
// let count them

x = x & 0x55555555 + (x >>  1) & 0x55555555;
                 // 0x00000000  0x0000002a  0x00001aaa

x = x & 0x33333333 + (x >>  2) & 0x33333333;
                 // 0x00000000  0x00000024  0x00001444

x = x & 0x0f0f0f0f + (x >>  4) & 0x0f0f0f0f;
                 // 0x00000000  0x00000006  0x00000508

x = x & 0x00ff00ff + (x >>  8) & 0x00ff00ff;
                 // 0x00000000  0x00000006  0x0000000d

x = x & 0x0000ffff + (x >> 16) & 0x0000ffff;
                 // 0x00000000  0x00000006  0x0000000d
// least sign.bit pos. was:  0           6          13

Ответ 17

Найден этот умный трюк с использованием "волшебных масок" в "Искусство программирования, часть 4", который делает это в O (log (n)) времени для n-разрядного числа. [с логическим (n) дополнительным пространством]. Типичные проверки решений для установленного бита - это O (n) или O (n) дополнительное пространство для таблицы поиска, поэтому это хороший компромисс.

Магические маски:

m0 = (...............01010101)  
m1 = (...............00110011)
m2 = (...............00001111)  
m3 = (.......0000000011111111)
....

Основная идея: Нет конечных нулей в x = 1 * [(x и m0) = 0] + 2 * [(x и m1) = 0] + 4 * [(x и m2) = 0] +...

int lastSetBitPos(const uint64_t x) {
    if (x == 0)  return -1;

    //For 64 bit number, log2(64)-1, ie; 5 masks needed
    int steps = log2(sizeof(x) * 8); assert(steps == 6);
    //magic masks
    uint64_t m[] = { 0x5555555555555555, //     .... 010101
                     0x3333333333333333, //     .....110011
                     0x0f0f0f0f0f0f0f0f, //     ...00001111
                     0x00ff00ff00ff00ff, //0000000011111111 
                     0x0000ffff0000ffff, 
                     0x00000000ffffffff };

    //Firstly extract only the last set bit
    uint64_t y = x & -x;

    int trailZeros = 0, i = 0 , factor = 0;
    while (i < steps) {
        factor = ((y & m[i]) == 0 ) ? 1 : 0;
        trailZeros += factor * pow(2,i);
        ++i;
    }
    return (trailZeros+1);
}

Ответ 18

Если С++ 11 доступен для вас, компилятор иногда может выполнить задачу для вас:)

constexpr std::uint64_t lssb(const std::uint64_t value)
{
    return !value ? 0 : (value % 2 ? 1 : lssb(value >> 1) + 1);
}

Результат - это индекс на основе 1.

Ответ 19

Это касается ответа @Anton Tykhyy

Вот моя реализация С++ 11 constexpr, устраняющая броски и удаление предупреждения на VС++ 17 путем обрезания 64-битного результата до 32 бит:

constexpr uint32_t DeBruijnSequence[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
};
constexpr uint32_t ffs ( uint32_t value )
{
    return  DeBruijnSequence[ 
        (( ( value & ( -static_cast<int32_t>(value) ) ) * 0x077CB531ULL ) & 0xFFFFFFFF)
            >> 27];
}

Чтобы обойти проблему 0x1 и 0x0, возвращающих 0, вы можете:

constexpr uint32_t ffs ( uint32_t value )
{
    return (!value) ? 32 : DeBruijnSequence[ 
        (( ( value & ( -static_cast<int32_t>(value) ) ) * 0x077CB531ULL ) & 0xFFFFFFFF)
            >> 27];
}

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

Наконец, если интересно, вот список статических утверждений, чтобы проверить, что код делает то, что предназначено для:

static_assert (ffs(0x1) == 0, "Find First Bit Set Failure.");
static_assert (ffs(0x2) == 1, "Find First Bit Set Failure.");
static_assert (ffs(0x4) == 2, "Find First Bit Set Failure.");
static_assert (ffs(0x8) == 3, "Find First Bit Set Failure.");
static_assert (ffs(0x10) == 4, "Find First Bit Set Failure.");
static_assert (ffs(0x20) == 5, "Find First Bit Set Failure.");
static_assert (ffs(0x40) == 6, "Find First Bit Set Failure.");
static_assert (ffs(0x80) == 7, "Find First Bit Set Failure.");
static_assert (ffs(0x100) == 8, "Find First Bit Set Failure.");
static_assert (ffs(0x200) == 9, "Find First Bit Set Failure.");
static_assert (ffs(0x400) == 10, "Find First Bit Set Failure.");
static_assert (ffs(0x800) == 11, "Find First Bit Set Failure.");
static_assert (ffs(0x1000) == 12, "Find First Bit Set Failure.");
static_assert (ffs(0x2000) == 13, "Find First Bit Set Failure.");
static_assert (ffs(0x4000) == 14, "Find First Bit Set Failure.");
static_assert (ffs(0x8000) == 15, "Find First Bit Set Failure.");
static_assert (ffs(0x10000) == 16, "Find First Bit Set Failure.");
static_assert (ffs(0x20000) == 17, "Find First Bit Set Failure.");
static_assert (ffs(0x40000) == 18, "Find First Bit Set Failure.");
static_assert (ffs(0x80000) == 19, "Find First Bit Set Failure.");
static_assert (ffs(0x100000) == 20, "Find First Bit Set Failure.");
static_assert (ffs(0x200000) == 21, "Find First Bit Set Failure.");
static_assert (ffs(0x400000) == 22, "Find First Bit Set Failure.");
static_assert (ffs(0x800000) == 23, "Find First Bit Set Failure.");
static_assert (ffs(0x1000000) == 24, "Find First Bit Set Failure.");
static_assert (ffs(0x2000000) == 25, "Find First Bit Set Failure.");
static_assert (ffs(0x4000000) == 26, "Find First Bit Set Failure.");
static_assert (ffs(0x8000000) == 27, "Find First Bit Set Failure.");
static_assert (ffs(0x10000000) == 28, "Find First Bit Set Failure.");
static_assert (ffs(0x20000000) == 29, "Find First Bit Set Failure.");
static_assert (ffs(0x40000000) == 30, "Find First Bit Set Failure.");
static_assert (ffs(0x80000000) == 31, "Find First Bit Set Failure.");

Ответ 20

Вот одна простая альтернатива, хотя поиск журналов немного дорого.

if(n == 0)
  return 0;
return log2(n & -n)+1;   //Assuming the bit index starts from 1

Ответ 21

Спасибо Эфименту за подсказку фс.

Встроенный поиск ffsl для позиции наименьшего значащего набора Бит очень эффективен. Следующие временные рамки, которые я получил для 100 млн 64-битных целых чисел без знака. Я скомпилировал с gcc 64-битную адресацию ORACLE LINUX и получил из clock_gettime (CLOCK_MONOTONIC, & measure) следующие часы выполнения настенных часов

встроенный ffsl() 0,188799 сек

побитовое И 0,180520 сек

побитовое ИЛИ 0,179663 сек

поразрядно НЕ 0,162763 сек

Сдвиг (справа) 0,168667 сек

целое умножение 0,211721 сек

целое добавление 0,168031 сек

Ответ 22

Недавно я вижу, что премьер-министр Сингапура опубликовал программу, которую он написал на facebook, есть одна строка, чтобы упомянуть ее.

Логика - это просто "значение и -значение", предположим, что у вас есть 0x0FF0,  0FF0 и (F00F + 1), что равно 0x0010, что означает, что самый низкий 1 находится в 4-м бит..:)

Ответ 23

Если у вас есть ресурсы, вы можете пожертвовать памятью, чтобы улучшить скорость:

static const unsigned bitPositions[MAX_INT] = { 0, 0, 1, 0, 2, /* ... */ };

unsigned GetLowestBitPos(unsigned value)
{
    assert(value != 0); // handled separately
    return bitPositions[value];
}

Примечание. В этой таблице будет потребляться не менее 4 ГБ (16 ГБ, если оставить тип возврата как unsigned). Это пример торговли одним ограниченным ресурсом (ОЗУ) для другого (скорость выполнения).

Если ваша функция должна оставаться переносной и работать как можно быстрее любой ценой, это будет путь. В большинстве приложений реального мира таблица 4 ГБ нереалистична.