Найти n-й бит SET в int - PullRequest
       9

Найти n-й бит SET в int

19 голосов
/ 06 октября 2011

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

Например, скажем, у меня есть:
0000 1101 1000 0100 1100 1000 1010 0000

И яхочу найти 4-й бит, который установлен.Затем я хочу, чтобы он возвратил:
0000 0000 0000 0000 0100 0000 0000 0000

Если popcnt(v) < n, было бы разумно, если бы эта функция вернула 0, но любое поведение для этого случая приемлемо для меня.

Я ищу что-то более быстрое, чем цикл, если возможно.

Ответы [ 6 ]

23 голосов
/ 13 декабря 2014

В настоящее время это очень просто с PDEP из набора команд BMI2 . Вот 64-битная версия с некоторыми примерами:

#include <cassert>
#include <cstdint>
#include <x86intrin.h>

inline uint64_t nthset(uint64_t x, unsigned n) {
    return _pdep_u64(1ULL << n, x);
}

int main() {
    assert(nthset(0b0000'1101'1000'0100'1100'1000'1010'0000, 0) ==
                  0b0000'0000'0000'0000'0000'0000'0010'0000);
    assert(nthset(0b0000'1101'1000'0100'1100'1000'1010'0000, 1) ==
                  0b0000'0000'0000'0000'0000'0000'1000'0000);
    assert(nthset(0b0000'1101'1000'0100'1100'1000'1010'0000, 3) ==
                  0b0000'0000'0000'0000'0100'0000'0000'0000);
    assert(nthset(0b0000'1101'1000'0100'1100'1000'1010'0000, 9) ==
                  0b0000'1000'0000'0000'0000'0000'0000'0000);
    assert(nthset(0b0000'1101'1000'0100'1100'1000'1010'0000, 10) ==
                  0b0000'0000'0000'0000'0000'0000'0000'0000);
}
10 голосов
/ 06 октября 2011

Оказывается, это действительно возможно сделать без петель.Проще всего предварительно рассчитать (как минимум) 8-битную версию этой проблемы.Конечно, эти таблицы занимают место в кеше, но все равно должно быть чистое ускорение практически во всех современных компьютерных сценариях.В этом коде n = 0 возвращает наименьший установленный бит, n = 1 - наименьший и т. Д.

Решение с помощью __popcnt

Существует решениеиспользование встроенной функции __popcnt (вам нужно, чтобы __popcnt был чрезвычайно быстрым, иначе любой выигрыш в производительности по сравнению с простым циклическим решением будет спорным. К счастью, большинство процессоров эры SSE4 + поддерживают его).

// lookup table for sub-problem: 8-bit v
byte PRECOMP[256][8] = { .... } // PRECOMP[v][n] for v < 256 and n < 8

ulong nthSetBit(ulong v, ulong n) {
    ulong p = __popcnt(v & 0xFFFF);
    ulong shift = 0;
    if (p <= n) {
        v >>= 16;
        shift += 16;
        n -= p;
    }
    p = __popcnt(v & 0xFF);
    if (p <= n) {
        shift += 8;
        v >>= 8;
        n -= p;
    }

    if (n >= 8) return 0; // optional safety, in case n > # of set bits
    return PRECOMP[v & 0xFF][n] << shift;
}

Это иллюстрирует, как разделить и победитьподход работает.

Общее решение

Существует также решение для "общих" архитектур - без __popcnt.Это можно сделать обработкой 8-битных блоков.Вам нужна еще одна справочная таблица, которая сообщает вам popcnt байта:

byte PRECOMP[256][8] = { .... } // PRECOMP[v][n] for v<256 and n < 8
byte POPCNT[256] = { ... } // POPCNT[v] is the number of set bits in v. (v < 256)

ulong nthSetBit(ulong v, ulong n) {
    ulong p = POPCNT[v & 0xFF];
    ulong shift = 0;
    if (p <= n) {
        n -= p;
        v >>= 8;
        shift += 8;
        p = POPCNT[v & 0xFF];
        if (p <= n) {
            n -= p;
            shift += 8;
            v >>= 8;
            p = POPCNT[v & 0xFF];
            if (p <= n) {
                n -= p;
                shift += 8;
                v >>= 8;
            }
        }
    }

    if (n >= 8) return 0; // optional safety, in case n > # of set bits
    return PRECOMP[v & 0xFF][n] << shift;
}

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

9 голосов
/ 06 октября 2011

v-1 имеет ноль, где v имеет младший значащий бит «один», в то время как все старшие биты одинаковы.Это приводит к следующей функции:

int ffsn(unsigned int v, int n) {
   for (int i=0; i<n-1; i++) {
      v &= v-1; // remove the least significant bit
   }
   return v & ~(v-1); // extract the least significant bit
}
1 голос
/ 06 октября 2011
def bitN (l: Long, i: Int) : Long = {
  def bitI (l: Long, i: Int) : Long = 
    if (i == 0) 1L else 
    2 * { 
      if (l % 2 == 0) bitI (l / 2, i) else bitI (l /2, i-1) 
    }
  bitI (l, i) / 2
}

Рекурсивный метод (в Scala). Уменьшить i, позиция, если по модулю 2 - 1. При возврате умножить на 2. Поскольку умножение вызывается как последняя операция, оно не является хвостовой рекурсивной, но, поскольку длинные имеют заранее известный размер, максимальный стек не слишком большой.

scala> n.toBinaryString.replaceAll ("(.{8})", "$1 ")
res117: java.lang.String = 10110011 11101110 01011110 01111110 00111101 11100101 11101011 011000

scala> bitN (n, 40) .toBinaryString.replaceAll ("(.{8})", "$1 ")
res118: java.lang.String = 10000000 00000000 00000000 00000000 00000000 00000000 00000000 000000
1 голос
/ 06 октября 2011

Я не могу увидеть метод без цикла, какие бы умысли не возникли;

int set = 0;
int pos = 0;
while(set < n) {
   if((bits & 0x01) == 1) set++;
   bits = bits >> 1;
   pos++;
}

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

Единственное, о чем я могу думать, это подход «разделяй и властвуй», который может дать O (log (n)), а не O (n) ... но, вероятно, нет.

Изменить: вы сказали любое поведение, так что не прекращение в порядке, верно?: P

0 голосов
/ 13 августа 2016

Опираясь на ответ, данный Джуккой Суомела, который использует машинно-зависимую инструкцию, которая может быть необязательно доступной, также можно написать функцию, которая делает то же самое, что и _pdep_u64 без каких-либо машинных зависимостей.Он должен перебирать установленные биты в одном из аргументов, но все еще может быть описан как функция constexpr для C ++ 11.

constexpr inline uint64_t deposit_bits(uint64_t x, uint64_t mask, uint64_t b, uint64_t res) {
    return mask != 0 ? deposit_bits(x, mask & (mask - 1), b << 1, ((x & b) ? (res | (mask & (-mask))) : res)) : res;
}

constexpr inline uint64_t nthset(uint64_t x, unsigned n)  {
    return deposit_bits(1ULL << n, x, 1, 0);
}
...