Посчитать количество единиц в числе

468
07 марта 2017, 17:05

Вопрос предельно прост: надо посчитать количество единиц в двоичном представлении числа за О(1). Линии и логарифмы даже не предлагайте. Интересует только О(1).

Answer 1

Ну, я, как обычно, не могу без экспериментов :) Итак, варианты -

// Hackers' Delight
int popHD(unsigned int x)
{
    x = x - ((x>>1) & 0x55555555);
    x = (x & 0x33333333) + ((x >>2) & 0x33333333);
    x = (x + (x>>4)) & 0x0F0F0F0F;
    x = x + (x >> 8);
    x = x + (x >> 16);
    return x & 0x3F;
}
int popLong(unsigned int x)
{
    int n = 0;
    while(x)
    {
        if (x&0x1) ++n;
        x >>= 1;
    }
    return n;
}
int popKern(unsigned int x)
{
    int n = 0;
    for (; x; n++)
        x &= (x - 1); // убираем младший бит
    return n;
}
int pop8(unsigned int x)
{
    static int count8[] =
    {
        // Из-за размера таблица удалена
    };
    return count8[x&0xFF] + count8[(x >> 8)&0xFF]
        + count8[(x >> 16)&0xFF] + count8[(x >> 24)&0xFF];
}
int pop16(unsigned int x)
{
    static int count16[] =
    {
        // Из-за размера таблица удалена
    };
    return count16[x&0xFFFF]+count16[(x>>16)&0xFFFF];
}
__popcnt(unsigned int) // MS VC++ specific

Если кому хочется посмотреть весь код - нет вопросов: http://vpaste.net/fD5DV

Далее набиваю вектор из 100000000 значений:

const int Count = 100000000;
vector<unsigned int> v;
v.reserve(Count);
for(int i = 0; i < Count; ++i)
    v.push_back(dis(eng));

Ну, а все тесты имеют один вид:

{
    int count = 0;
    muTimer mu;  // Мой таймер
    for(unsigned int x: v) count += __popcnt(x);
    cout << count << "\n";
}

Для кэширования один проход делаю без засекания времени.

Вот как выглядят результаты на моей машине:

popHD    popLong    popKern     pop8      pop16     __popcnt
------------------------------------------------------------
89 ms    2514 ms    1505 ms    215 ms    200 ms       83 ms
88 ms    2525 ms    1482 ms    210 ms    195 ms       82 ms

Понятно, что от раза к разу пляшет, но несильно - для того и показываю два результата.

Выводы делайте сами :) Чистое суммирование

{
    int count = 0;
    muTimer mu;  // Мой таймер
    for(unsigned int x: v) count += x;
    cout << count << "\n";
}

дает примерно 25-27 ms.

Update
Посмотрел ассемблер. popHD сразу по 4 числа работает, с xmm регистрами, так что не знаю даже, радоваться или огорчаться :) Для отдельного значения __popcnt, понятно, быстрее...

Answer 2

Да не вопрос.

unsigned int count = 0;
for (; n; n <<= 1)
    count += n & 1;

Всего не более CHAR_BIT * sizeof(n) итераций, то есть, ограничено константой.

Вот ещё вам классический Кернигановский способ:

unsigned int count = 0;
for (; n; count++)
    n &= (n - 1); // убираем младший бит

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

Подборка различных способов подсчёта битов есть тут.

Answer 3

Создайте lookup-таблицу для 8-битных байтов (и/или 16-битных слов) и затем примените ее для подсчета битов в типе любого размера. Пока размер рассматриваемых типов константен, время подсчета тоже является константным.

Является ли такой подход (как, впрочем, и любой другой) O(1) - это уже у вас надо спрашивать.

P.S. Я обычно не мелочусь и сразу забабахиваю таблицу для 32-битных слов. Солидная таблица для солидных господ.

Answer 4

В gсс/g++ можете воспользоваться встроенной функцией

Built-in Function: int __builtin_popcount (unsigned int x)

Returns the number of 1-bits in x.

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

P.S.

для X86 в gcc (g++) семейство функций __builtin_popcount было реализовано на основе таблицы из 256 элементов (на март 2017 посмотрел disasm в gdb и увидел реализацию как в int pop(unsigned long long x) в ответе @Harry (c 0x5555555555555555 и т.д.));

по крайней мере в clang и icc эта функция (__builtin_popcount) называется так же, а в MSVC ее зовут __popcnt;

Answer 5

Ну, например, для 64-битового unsigned long long:

int pop(unsigned long long x)
{
    x = (x & 0x5555555555555555) + ((x >>  1) & 0x5555555555555555);
    x = (x & 0x3333333333333333) + ((x >>  2) & 0x3333333333333333);
    x = (x & 0x0F0F0F0F0F0F0F0F) + ((x >>  4) & 0x0F0F0F0F0F0F0F0F);
    x = (x & 0x00FF00FF00FF00FF) + ((x >>  8) & 0x00FF00FF00FF00FF);
    x = (x & 0x0000FFFF0000FFFF) + ((x >> 16) & 0x0000FFFF0000FFFF);
    x = (x & 0x00000000FFFFFFFF) + ((x >> 32) & 0x00000000FFFFFFFF);
    return x & 0x0000000000007F;
}

Или вариант для 32-битного:

int pop(unsigned long long x)
{
    x = (x & 0x55555555) + ((x >>  1) & 0x55555555);
    x = (x & 0x33333333) + ((x >>  2) & 0x33333333);
    x = (x & 0x0F0F0F0F) + ((x >>  4) & 0x0F0F0F0F);
    x = (x & 0x00FF00FF) + ((x >>  8) & 0x00FF00FF);
    x = (x & 0x0000FFFF) + ((x >> 16) & 0x0000FFFF);
    return x & 0x3F;
}

Он же, слегка переделанный:

int pop(unsigned long x)
{
    x = x - ((x>>1) & 0x55555555);
    x = (x & 0x33333333) + ((x >>2) & 0x33333333);
    x = (x + (x>>4)) & 0x0F0F0F0F;
    x = x + (x >> 8);
    x = x + (x >> 16);
    return x & 0x3F;
}
Answer 6

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

READ ALSO
Выбор СУБД для написания программы C++

Выбор СУБД для написания программы C++

ЗдравствуйтеС какой СУБД проще всего работать новичку в C++? На C# работал с Access, но теперь эта СУБД запрещена, поэтому нужна альтернатива

331
Как работает shared_mutex?

Как работает shared_mutex?

Не вполне понимаю, как работает shared_mutex в 17-ом стандарте или в boostТакая ситуация: несколько читателей одновременно захватывают этот мьютекс,...

305
Подсчет числа записей в бинарном файле

Подсчет числа записей в бинарном файле

Подскажите ,пожалуйста,в чем ошибка

316
Нужна помощь в написании драйвера на C++

Нужна помощь в написании драйвера на C++

Обшарив очень много форумов/сайтов не смог найти ничего полезного по поводу написания драйвера на C++Хочу написать простой драйвер, который...

287