Как подсчитать количество нулевых битов в целом?
Как я могу найти количество "нулевых" битов в C++. Предположим, у меня есть целое число;
int value = 276;
для которого у меня есть биты 100010100, но как подсчитать нули?
11 ответов
самый простой самый наивный способ-просто перебирать биты и считать:
size_t num_zeroes = 0;
for(size_t i = 0; i < CHAR_BIT * sizeof value; ++i)
{
if ((value & (1 << i)) == 0)
++num_zeroes;
}
есть все количество лучших (для разных значений "лучше") способов, но это довольно ясно, очень лаконично (с точки зрения кода) и не требует кучу настроек.
одна микро-оптимизация, которую можно считать улучшением, заключается в том, чтобы не вычислять маску для тестирования каждого бита, а сдвигать значение и всегда проверять самый правый бит:
for(size_t i = 0; i < CHAR_BIT * sizeof value; ++i, value >>= 1)
{
if ((value & 1) == 0)
++num_zeroes;
}
если вы хотите эффективности, то есть хорошая реализация в книге "хакеры восторг"
22 инструкции бесплатно.
unsigned int count_1bits(unsigned int x)
{
x = x - ((x >> 1) & 0x55555555);
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = x + (x >> 8);
x = x + (x >> 16);
return x & 0x0000003F;
}
unsigned int count_0bits(unsigned int x)
{
return 32 - count_1bits(x);
}
я попытаюсь объяснить, как это работает. Это алгоритм "разделяй и властвуй".
(x >> 1) & 0x55555555
сдвигает все биты на 1 шаг вправо и принимает наименее значительный бит каждой пары битов.
0x55555555 -> 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 (16x2 bit pairs)
таким образом, в основном у вас будет следующая таблица всех 2-битных перестановок.
1. (00 >> 1) & 01 = 00
2. (01 >> 1) & 01 = 00
3. (10 >> 1) & 01 = 01
4. (11 >> 1) & 01 = 01
x - ((x >> 1) & 0x55555555);
затем вы вычитаете их из несмещенных пар.
1. 00 - 00 = 00 => 0 x 1 bits
2. 01 - 00 = 01 => 1 x 1 bits
3. 10 - 01 = 01 => 1 x 1 bits
4. 11 - 01 = 10 => 2 x 1 bits
x = x - ((x >> 1) & 0x55555555);
Итак, теперь мы изменили каждую 2-битную пару так, чтобы их значение теперь было количеством битов их соответствующих исходных 2-битных пар... и тогда мы продолжаем аналогичным образом с 4-битных групп, 8 бит групп, 16 бит групп и окончательной 32 бит.
если вы хотите лучшее объяснение купите книгу, там много хороших объяснений и обсуждений альтернативных алгоритмов и т. д...
Если вы используете GCC, вы можете попробовать встроенные функции:
int __builtin_popcount (unsigned int x)
int __builtin_ctz (unsigned int x)
int __builtin_clz (unsigned int x)
посмотреть документация GCC для сведения.
Керниган способом подсчета бит
unsigned int v; // count the number of bits set in v
unsigned int c; // c accumulates the total bits set in v
for (c = 0; v; c++)
{
v &= v - 1; // clear the least significant bit set
}
может быть легко адаптирован для данной задачи. Количество итераций здесь равно количеству битов набора.
Я также рекомендую ссылку выше для различных других способов решения этой и других типов задач, связанных с битом. Существует также однострочный пример получения количества битов, реализованного в макросах.
на сегодняшний день наиболее очевидным решением является таблица поиска.
/* Assuming CHAR_BITS == 8 */
int bitsPerByte[256] = { 8, 7, 7, 6, /* ... */ };
int bitsInByte(unsigned char c) { return bits[c]; }
есть отличная книга для такого рода вещей: восторг хакера (да, название отстой : это не имеет ничего общего с безопасностью, но исключительно бит-twiddling). Он предоставляет несколько алгоритмов для подсчета бит "1", лучшее также можно найти здесь (хотя в книге есть объяснения, что этот сайт не так).
Как только вы узнаете количество бит "1", просто вычитайте его до количества бит в вашем представлении типа.
Я удивлен, что никто не упомянул этот:
int num_zero_bits = __builtin_popcount(~num);
это даст количество нулевых битов в num
при использовании с GCC.
сделайте комплимент, затем сосчитайте 1s.
count_zero_bits (x ) = count_one_bits (~x );
реализуйте код для подсчета единиц.
template< typename I >
int count_one_bits( I i )
{
size_t numbits = 0;
for( ; i != 0; i >>= 1 )
{
numbits += i&1;
}
}
хотя есть проблема с моей функцией, если я отрицательное число, потому что >> поместит 1 бит в правую сторону, так что вы получите бесконечный цикл. Если существует шаблонный способ применения неподписанного типа, который был бы идеальным.
после этого затем:
template< typename I > int count_zero_bits( I i )
{
return count_one_bits( ~i );
}
будет работать.
по моему мнению, самый простой способ получить количество нулевых битов в натуральном числе-это следующий фрагмент кода.
int get_zero_bit_count(int num)
{
int cnt = 0;
while(num > 0)
{
int and_num = num & 1;
if (and_num != num) cnt++;
num >>= 1;
}
return cnt;
}
этот фрагмент кода легко понять и является selp explainatory . Это хорошо работает для целых положительных чисел.
расширение ответа ронага, о котором упоминали другие пользователи, приводит к неправильным результатам (его алгоритм работает только до значения x = 15), вот обновленная версия алгоритма:
uint8_t count_1bits(uint32_t x) {
x = x - ((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;
}
uint8_t count_0bits(uint32_t x) {
return 32 - count_1bits(x);
}
объяснение первой строки из ronag является правильным, однако остальные строки используют другой подход. В первой строке, через смещение и вычитание, каждая 2-битная пара будет содержать количество битов, которые были установлены в этой паре в исходном числе. Этот остальные строки рекурсивно складывают эти числа вместе, добавляя lsb каждой 2n-разрядной группы к msb этой пары, сдвинутой на n, так что 2n-разрядная группа содержит количество битов, которое было установлено в этой группе в исходном номере:
01110110: 0111 (7 bits were set in the original number) 0110 (6 bits were set in the original number)
-> 01110110 & 00001111 + (01110110 >> 4) & 00001111
= 0110 + 0111
= 1101
приведенный выше алгоритм работает для 32-битных целых чисел, но может быть легко адаптирован путем изменения констант на правильную битовую длину, чтобы шаблон оставался неизменным (например, 0x5555... = 0101..., 0x0f0f... = 00001111... так далее.) и добавление / удаление соответствующих сдвигов