如何计算32位整数中的设置位数?


868

代表数字7的8位看起来像这样:

00000111

设置了三个位。

确定32位整数中的置位位数的算法是什么?


101
这是汉明重量BTW。
Purfideas

11
实际的应用程序是什么?(这不是批评,我很好奇。)
jonmorgan

8
奇偶校验位的计算(查找),用作通信中的简单错误检测。
Dialecticus 2010年

8
@Dialecticus,计算奇偶校验位比计算汉明权重便宜
finnw 2011年

15
@spookyjon假设您有一个表示为邻接矩阵的图,该图本质上是一组位。如果要计算顶点的边数,则可以归结为计算位集中一行的汉明权重。
2011年

Answers:


849

这就是所谓的“ 汉明重量 ”,“弹出计数”或“横向添加”。

“最佳”算法实际上取决于您所使用的CPU以及您的使用模式。

一些CPU具有单个内置指令来执行此操作,而其他CPU具有作用于位向量的并行指令。并行指令(如x86一样popcnt,在受支持的CPU上)几乎可以肯定是最快的。其他一些体系结构可能有一条慢指令,该指令由微编码循环实现,该循环每个周期测试一位(需要引用)。

如果您的CPU具有较大的缓存,并且/或者您正在紧凑的循环中执行大量这些指令,那么预填充的表查找方法可能会非常快。但是,由于“高速缓存未命中”的代价,它可能会遭受损失,在这种情况下,CPU必须从主内存中获取某些表。(分别查找每个字节以使表较小。)

如果您知道字节将大部分为0或大多数为1,那么对于这些​​情况,有非常有效的算法。

我相信以下是一种非常好的通用算法,称为“并行”或“可变精度SWAR算法”。我用类似C的伪语言表示了这一点,您可能需要对其进行调整以使其适用于特定的语言(例如,对于C ++使用uint32_t,而在Java中使用>>>):

int numberOfSetBits(uint32_t i)
{
     // Java: use int, and use >>> instead of >>
     // C or C++: use uint32_t
     i = i - ((i >> 1) & 0x55555555);
     i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
     return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}

对于JavaScript:强制转换为|0以提高性能:将第一行更改为i = (i|0) - ((i >> 1) & 0x55555555);

在所讨论的所有算法中,这都是最坏的情况,因此可以有效地处理您使用的所有使用模式或值。


该SWAR bithack的工作方式:

i = i - ((i >> 1) & 0x55555555);

第一步是屏蔽的优化版本,以隔离奇数/偶数位,进行移位以使其对齐并相加。这样可以有效地在2位累加器中执行16个单独的加法运算SWAR = A寄存器内的SIMD)。像(i & 0x55555555) + ((i>>1) & 0x55555555)

下一步将使用那些16x 2位累加器的奇/偶八,然后再次相加,产生8x 4位和。这次i - ...无法进行优化,因此仅在移位之前/之后进行屏蔽。对于需要在寄存器中分别构造32位常量的ISA进行编译时,0x33...两次使用相同的常量而不是0xccc...在移位之前使用都是好事。

最后的移位和加法步骤(i + (i >> 4)) & 0x0F0F0F0F扩展为4个8位累加器。由于在任何4位累加器中的最大值为,所以如果设置了相应输入位的所有4位,则它将加法之后而不是在之前进行掩码4。4 + 4 = 8仍然适合于4位,因此在中不能在半字节元素之间进位i + (i >> 4)

到目前为止,这只是使用SWAR技术并进行一些巧妙优化的相当普通的SIMD。继续使用相同的模式进行另外2个步骤,可以扩大到2x 16位和1x 32位计数。但是在硬件快速乘法的机器上,有一种更有效的方法:

一旦我们的“元素”不足,乘以魔术常数就可以将所有元素求和成顶层元素。在这种情况下,字节元素。乘法是通过左移和加法完成的,因此x * 0x01010101结果相乘x + (x<<8) + (x<<16) + (x<<24) 我们的8位元素是足够宽(并保持足够小的数值),这不会产生进位是前8位。

此版本的64位版本可以使用0x0101010101101010101乘法器以64位整数形式处理8x 8位元素,并使用提取高字节>>56。因此,它不需要采取任何额外的步骤,只需使用更宽的常量即可。__builtin_popcountllpopcnt未启用硬件指令时,这就是GCC 在x86系统上使用的功能。如果您可以为此使用内建函数或内在函数,请这样做使编译器有机会进行针对特定目标的优化。


具有完整的SIMD,可用于更宽的向量(例如,对整个数组进行计数)

这种逐位SWAR算法可以并行化以一次在多个矢量元素中完成,而不是在单个整数寄存器中完成,以提高具有SIMD但没有可用的popcount指令的CPU的速度。(例如,必须在任何CPU上运行的x86-64代码,而不仅仅是Nehalem或更高版本。)

但是,将向量指令用于popcount的最佳方法通常是使用可变混洗在每个字节并行的同时对4位进行表查找。(这4位索引了保存在向量寄存器中的16个条目表)。

在Intel CPU上,硬件64位popcnt指令的性能可以比SSSE3 PSHUFB位并行实现高2倍左右,但前提是您的编译器正确地执行该指令。否则,上证所可能会明显领先。较新的编译器版本意识到Intel上的popcnt错误依赖项 问题

参考文献:


87
哈!我喜欢NumberOfSetBits()函数,但是很幸运,可以通过代码审查获得它。:-)
Jason S

37
也许应该使用unsigned int,以轻松表明它没有任何符号位并发症。同样会uint32_t更安全,例如,您在所有平台上都能得到预期的结果?
Craig McQueen

35
@nonnb:实际上,按照编写的方式,该代码有错误,需要维护。>>为负值实现定义。该参数需要更改(或强制转换为)unsigned,并且由于代码是特定于32位的,因此可能应该使用uint32_t
R .. GitHub停止帮助ICE,

6
这不是真的魔术。它添加了一些位,但是这样做有一些巧妙的优化。答案中给出的Wikipedia链接很好地解释了正在发生的事情,但我将逐行介绍。1)计算每对位中的位数,然后将该数放入该对位中(您将拥有00、01或10);这里的“聪明”位是避免一个掩码的减法。2)将这些成对的比特对加到相应的半字节中;这里没有什么聪明的,但每个半字节现在将具有值0-4。(续)
dash-tom-bang 2012年

8
另一个要注意的是,通过简单地适当扩展常量,可以扩展到64位和128位寄存器。有趣的是(对我而言),这些常数也是〜0 / 3、5、17和255;前三个是2 ^ n + 1。您越凝视并在淋浴时考虑一下,所有这些都变得更加有意义。:)
dash-tom-bang

214

还请考虑编译器的内置函数。

例如,在GNU编译器上,您可以使用:

int __builtin_popcount (unsigned int x);
int __builtin_popcountll (unsigned long long x);

在最坏的情况下,编译器将生成对函数的调用。在最佳情况下,编译器将发出一条cpu指令以更快地完成相同的工作。

GCC内部函数甚至可以跨多个平台工作。Popcount将成为x86架构中的主流,因此现在就开始使用内在函数是有意义的。其他架构的流行人数已经很多年了。


在x86上,您可以告诉编译器它可以使用来支持popcnt指令,-mpopcnt或者-msse4.2也可以启用在同一代中添加的向量指令。请参阅GCC x86选项-march=nehalem(或者-march=您希望代码采用并调整的任何CPU)都是不错的选择。在较旧的CPU上运行生成的二进制文件将导致非法指令错误。

要针对在其上构建计算机的二进制文件进行优化,请使用-march=native (与gcc,clang或ICC一起使用)。

MSVC提供了x86 popcnt指令的内部函数,但与gcc不同,它实际上是硬件指令的内部函数,需要硬件支持。


使用std::bitset<>::count()而不是内置

从理论上讲,任何知道如何为目标CPU有效地增加计数的编译器都应通过ISO C ++公开该功能std::bitset<>。实际上,对于某些目标CPU,在某些情况下使用位破解AND / shift / ADD可能会更好。

对于硬件popcount是可选扩展(例如x86)的目标体系结构,并非所有编译器都std::bitset在可用时利用它。例如,MSVC无法popcnt在编译时启用支持,并且始终使用表查找,即使这样做/Ox /arch:AVX(这意味着SSE4.2,尽管从技术上讲,它有一个单独的功能位popcnt)。

但是至少您可以获得随处可见的便携式产品,并且使用带有正确目标选项的gcc / clang,您可以获得支持它的体系结构的硬件弹出数量。

#include <bitset>
#include <limits>
#include <type_traits>

template<typename T>
//static inline  // static if you want to compile with -mpopcnt in one compilation unit but not others
typename std::enable_if<std::is_integral<T>::value,  unsigned >::type 
popcount(T x)
{
    static_assert(std::numeric_limits<T>::radix == 2, "non-binary type");

    // sizeof(x)*CHAR_BIT
    constexpr int bitwidth = std::numeric_limits<T>::digits + std::numeric_limits<T>::is_signed;
    // std::bitset constructor was only unsigned long before C++11.  Beware if porting to C++03
    static_assert(bitwidth <= std::numeric_limits<unsigned long long>::digits, "arg too wide for std::bitset() constructor");

    typedef typename std::make_unsigned<T>::type UT;        // probably not needed, bitset width chops after sign-extension

    std::bitset<bitwidth> bs( static_cast<UT>(x) );
    return bs.count();
}

在Godbolt编译器资源管理器上查看来自gcc,clang,icc和MSVC的asm。

x86-64 gcc -O3 -std=gnu++11 -mpopcnt发出此消息:

unsigned test_short(short a) { return popcount(a); }
    movzx   eax, di      # note zero-extension, not sign-extension
    popcnt  rax, rax
    ret
unsigned test_int(int a) { return popcount(a); }
    mov     eax, edi
    popcnt  rax, rax
    ret
unsigned test_u64(unsigned long long a) { return popcount(a); }
    xor     eax, eax     # gcc avoids false dependencies for Intel CPUs
    popcnt  rax, rdi
    ret

PowerPC64 gcc -O3 -std=gnu++11发出(对于intarg版本):

    rldicl 3,3,0,32     # zero-extend from 32 to 64-bit
    popcntd 3,3         # popcount
    blr

此源完全不是特定于x86或特定于GNU的,而仅适用于带有gcc / clang / icc的x86。

还要注意,gcc对于没有单指令popcount的体系结构的后备方式是按字节一次表查找。例如,对于ARM来说不是很好。


5
我同意这通常是个好习惯,但是在XCode / OSX / Intel上,我发现它生成的代码比此处发布的大多数建议的代码慢。请参阅我的答案以获取详细信息。

5
英特尔i5 / i7具有使用通用寄存器执行的SSE4指令POPCNT。我的系统上的GCC不会使用此内在函数发出该指令,我想是因为还没有-march = nehalem选项。
matja

3
@matja,如果我使用-msse4.2进行编译,则我的GCC 4.4.1会发出popcnt指令
Nils Pipenbrinck

74
使用c ++的std::bitset::count。内联后,将其编译为单个__builtin_popcount调用。
deft_code

1
@nlucaroni好吧,是的。时代在变。我已经在2008年写下了这个答案。如今,我们有了本机popcount,如果平台允许,内在函数将编译为单个汇编程序语句。
Nils Pipenbrinck

183

我认为,“最佳”解决方案是可以由另一位程序员(或两年后的原始程序员)阅读而无需大量评论的解决方案。您可能想要一些已经提供的最快或最聪明的解决方案,但是我总是更喜欢可读性而不是聪明。

unsigned int bitCount (unsigned int value) {
    unsigned int count = 0;
    while (value > 0) {           // until all bits are zero
        if ((value & 1) == 1)     // check lower bit
            count++;
        value >>= 1;              // shift bits, removing lower bit
    }
    return count;
}

如果您想提高速度(并假设您很好地记录在案以帮助您的后继者),则可以使用表查找:

// Lookup table for fast calculation of bits set in 8-bit unsigned char.

static unsigned char oneBitsInUChar[] = {
//  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F (<- n)
//  =====================================================
    0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, // 0n
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, // 1n
    : : :
    4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, // Fn
};

// Function for fast calculation of bits set in 16-bit unsigned short.

unsigned char oneBitsInUShort (unsigned short x) {
    return oneBitsInUChar [x >>    8]
         + oneBitsInUChar [x &  0xff];
}

// Function for fast calculation of bits set in 32-bit unsigned int.

unsigned char oneBitsInUInt (unsigned int x) {
    return oneBitsInUShort (x >>     16)
         + oneBitsInUShort (x &  0xffff);
}

尽管这些依赖于特定的数据类型大小,所以它们不那么可移植。但是,由于许多性能优化都不是可移植的,因此这可能不是问题。如果您想要便携性,我会坚持使用可读的解决方案。


21
而不是除以2并将其注释为“移位位...”,您应该仅使用移位运算符(>>)并保留注释。
indiv 2009年

9
替换if ((value & 1) == 1) { count++; }为更有意义count += value & 1吗?
Ponkadoodle

21
不,在这种情况下,最好的解决方案不是最易读的解决方案。最好的算法是最快的算法。
NikiC

21
@nikic,这完全是您的意见,尽管您显然可以随意否决我。问题中没有提到如何量化“最佳”,“性能”或“快速”一词无处可寻。这就是为什么我选择可读性。
paxdiablo

3
三年后,我正在阅读此答案,由于它可读性强且包含更多评论,因此我认为它是最佳答案。期。
waka-waka-waka13年

98

摘自《骇客的喜悦》,第2页。66,图5-2

int pop(unsigned 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 & 0x0000003F;
}

以约20位数的指令(取决于拱形)执行,无分支。

黑客的喜悦 令人愉快的!强烈推荐。


8
Java方法Integer.bitCount(int)使用相同的确切实现。
Marco Bolis 2015年

这样做有点麻烦-如果仅关心16位值而不是32位,它将如何改变?
杰里米·布鲁姆

也许让黑客高兴是一件令人愉快的事,但是我会给任何一个称呼它pop而不是population_count(或者pop_cnt如果您必须缩写)的人以一个好主意。@MarcoBolis我认为这对所有版本的Java都是正确的,但正式地说,这将取决于实现:)
Maarten Bodewes

而且,这不需要乘法,就像接受的答案中的代码一样。
Alex

请注意,将其推广到64位存在一个问题。由于存在掩码,结果不能为64。
艾伯特·范德霍斯特

76

我认为最快的方法-不使用查找表和popcount-是以下方法。它仅需12次操作即可计数设置的位。

int popcount(int v) {
    v = v - ((v >> 1) & 0x55555555);                // put count of each 2 bits into those 2 bits
    v = (v & 0x33333333) + ((v >> 2) & 0x33333333); // put count of each 4 bits into those 4 bits  
    return c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
}

之所以起作用,是因为您可以将设置的总数除以两半,然后对两个设置的位数进行计数,然后将它们相加即可。也称为Divide and Conquer范式。让我们详细一点。

v = v - ((v >> 1) & 0x55555555); 

位的两个比特的数目可以是0b000b010b10。让我们尝试在2位上解决这个问题。

 ---------------------------------------------
 |   v    |   (v >> 1) & 0b0101   |  v - x   |
 ---------------------------------------------
   0b00           0b00               0b00   
   0b01           0b00               0b01     
   0b10           0b01               0b01
   0b11           0b01               0b10

这就是所需要的:最后一列显示每两个位对中的置位数。如果>= 2 (0b10)然后and产生两位数0b01,则产生0b00

v = (v & 0x33333333) + ((v >> 2) & 0x33333333); 

这句话应该很容易理解。第一次操作后,我们每2位就有一个设置位的计数,现在我们每4位就累加该计数。

v & 0b00110011         //masks out even two bits
(v >> 2) & 0b00110011  // masks out odd two bits

然后,我们将上述结果相加,得出4位中设置位的总数。最后的陈述是最棘手的。

c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;

让我们进一步分解...

v + (v >> 4)

它类似于第二条语句。我们将以4为一组计数设置的位。由于我们之前的操作,我们知道每个半字节都具有设置位的计数。让我们来看一个例子。假设我们有字节0b01000010。这意味着第一个半字节设置为4位,第二个半字节设置为2位。现在,我们将这些半字节加在一起。

0b01000010 + 0b01000000

它为我们提供了第一个半字节中字节中设置的位的计数,0b01100010因此我们屏蔽了数字中所有字节的最后四个字节(将其丢弃)。

0b01100010 & 0xF0 = 0b01100000

现在,每个字节都有设置位的计数。我们需要将它们加在一起。诀窍是将结果乘以0b10101010具有有趣属性的结果。如果我们的数字有四个字节,A B C D它将产生一个包含这些字节的新数字A+B+C+D B+C+D C+D D。一个4字节的数字最多可以设置32位,可以表示为0b00100000

现在我们需要的是第一个字节,该字节具有所有字节中所有设置位的总和,并通过得出 >> 24。该算法是针对32 bit单词设计的,但可以轻松地对其进行修改64 bit


什么是c = 什么?看起来应该被淘汰了。此外,建议使用额外的paren集A“((((v +(v >> 4))&0xF0F0F0F)* 0x1010101)>> 24”,以避免出现一些经典的警告。
chux-恢复莫妮卡

4
一个重要的功能是,此32位例程适用于popcount(int v)popcount(unsigned v)。对于可移植性,请考虑popcount(uint32_t v),等等。确实类似于* 0x1010101部分。
chux-恢复莫妮卡

酱 ?(书籍,链接,invetors的名字等)将非常受欢迎。因为这样我们可以将其粘贴到我们的代码库中,并附上注释。
v.oddou

1
我认为为了更清楚起见,最后一行应写为:return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;因此我们不需要计算字母就可以查看您的实际操作(因为您丢弃了第一个字符0,所以我不小心认为您使用了错误的(翻转的)位模式作为掩码-直到我注意到只有7个字母,而不是8个)。
–emem

乘法通过0x01010101可能是缓慢的,这取决于处理器。例如,在我的旧版PowerBook G4中,1倍的乘法运算大约慢于4次加法(不如除法那么糟糕,其中1格的分解约慢于23次加法)。
乔治·科勒

54

我很无聊,并为三种方法的十亿次迭代计时。编译器是gcc -O3。CPU是他们在第一代Macbook Pro中使用的。

最快的速度是3.7秒:

static unsigned char wordbits[65536] = { bitcounts of ints between 0 and 65535 };
static int popcount( unsigned int i )
{
    return( wordbits[i&0xFFFF] + wordbits[i>>16] );
}

第二位使用相同的代码,但查找4个字节而不是2个半字。那花了大约5.5秒。

第三名是令人费解的“横向添加”方法,耗时8.6秒。

排名第四的是GCC的__builtin_popcount(),可耻的是11秒。

一次计数一次的方法要慢得多,而我无聊地等待它完成。

因此,如果您最关心性能,请使用第一种方法。如果您关心,但不足以在上面花费64Kb的RAM,请使用第二种方法。否则,使用可读的(但很慢)一次一位的方法。

很难想到您想使用位旋转方法的情况。

编辑:这里类似的结果。


49
@Mike,如果表位于缓存中,那么基于表的方法是无与伦比的。这在微观基准测试中会发生(例如,在紧密的循环中进行数百万次测试)。但是,缓存未命中大约需要200个周期,即使是最幼稚的弹出计数也会在此处更快。它始终取决于应用程序。
尼尔斯·派宾布林克

10
如果您没有在一个紧密的循环中调用该例程数百万次,那么您根本就不必担心它的性能,并且不妨使用幼稚但易读的方法,因为性能损失可以忽略不计。和FWIW一样,8位LUT在10到20个调用中变得很热。

6
我认为很难想象这种情况是通过该方法进行的叶子调用(实际上是在您的应用中完成了繁重的工作)。根据发生的其他事情(和线程),较小的版本可能会获胜。由于引用的位置更好,因此已经写出了许多击败同行的算法。为什么也没有呢?
杰森

用clang尝试一下,在实现内建函数方面要聪明得多。
马特·乔纳

3
除非使用-msse4.2调用,否则GCC不会发出popcont指令,这种情况比“横向添加”要快。
lvella 2012年

54

如果您恰巧使用Java,则内置方法Integer.bitCount将执行此操作。


当sun提供了不同的API时,它必须在后台使用某种逻辑,对吗?
Vallabh Patade

2
附带说明一下,Java的实现使用了Kevin Little指出的相同算法。
Marco Bolis 2015年

2
实施不谈,这大概是意图为开发者维护你的代码(或当你回来吧6个月更高版本)后,最清晰的信息
divillysausages

31
unsigned int count_bit(unsigned int 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;
}

让我解释一下这种算法。

该算法基于分而治之算法。假设有一个8位整数213(二进制为1010101),该算法的工作原理如下(每次合并两个相邻块):

+-------------------------------+
| 1 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |  <- x
|  1 0  |  0 1  |  0 1  |  0 1  |  <- first time merge
|    0 0 1 1    |    0 0 1 0    |  <- second time merge
|        0 0 0 0 0 1 0 1        |  <- third time ( answer = 00000101 = 5)
+-------------------------------+

7
该算法是Matt Howells发布的版本,然后对其进行了优化,以使其变得难以阅读。
Lefteris E

29

这是有助于您了解微体系结构的问题之一。我刚刚在使用-O3的gcc 4.3.3下使用C ++内联函数对两个变量进行定时,以消除函数调用开销,十亿次迭代,保留所有计数的总和以确保编译器不会删除任何重要内容,使用rdtsc进行定时(时钟周期精确)。

内联int pop2(无符号x,无符号y)
{
    x = x-((x >> 1)&0x55555555);
    y = y-((y >> 1)&0x55555555);
    x =(x&0x33333333)+((x >> 2)&0x33333333);
    y =(y&0x33333333)+((y >> 2)&0x33333333);
    x =(x +(x >> 4))&0x0F0F0F0F;
    y =(y +(y >> 4))&0x0F0F0F0F;
    x = x +(x >> 8);
    y = y +(y >> 8);
    x = x +(x >> 16);
    y = y +(y >> 16);
    返回(x + y)&0x000000FF;
}

未经修改的Hacker's Delight花费了12.2 gigacycles。我的并行版本(计数为两倍)在13.0千兆周期内运行。两者在2.4GHz Core Duo上的总耗时为10.5s。25 gigacycles =刚好超过10秒(在此时钟频率下),所以我相信我的时间是正确的。

这与指令依赖链有关,这对于该算法非常不利。通过使用一对64位寄存器,我可以将速度再次提高近一倍。实际上,如果我很聪明,并尽快添加x + ya,我就可以避免一些麻烦了。带有一些细微调整的64位版本甚至可以算出来,但计数又是原来的两倍。

使用128位SIMD寄存器(又是两倍),SSE指令集也常常具有巧妙的捷径。

没有理由使代码特别透明。界面简单,算法可以在很多地方在线引用,并且可以进行全面的单元测试。偶然发现它的程序员甚至可能学到一些东西。这些位操作在机器级别极为自然。

好的,我决定使用经过调整的64位版本。对于这个sizeof(unsigned long)== 8

内联int pop2(无符号长x,无符号长y)
{
    x = x-((x >> 1)&0x5555555555555555);
    y = y-((y >> 1)&0x5555555555555555);
    x =(x和0x3333333333333333)+((x >> 2)&0x3333333333333333);
    y =(y&0x3333333333333333)+((y >> 2)&0x3333333333333333);
    x =(x +(x >> 4))&0x0F0F0F0F0F0F0F0F;
    y =(y +(y >> 4))&0x0F0F0F0F0F0F0F0F;
    x = x + y; 
    x = x +(x >> 8);
    x = x +(x >> 16);
    x = x +(x >> 32); 
    返回x&0xFF;
}

看起来不错(尽管我没有仔细测试)。现在的时机为10.70吉比特/ 14.1吉比特。后面的数字总计1,280亿位,相当于该计算机上经过的5.9s。非并行版本的速度有所提高,因为我正在64位模式下运行,并且它喜欢64位寄存器比32位寄存器稍好。

让我们看看这里是否还有更多的OOO流水线。这涉及更多,因此我实际上进行了一些测试。每一项的总和为64,所有总和为256。

内联int pop4(无符号长x,无符号长y, 
                unsigned long u,unsigned long v)
{
  枚举{m1 = 0x5555555555555555, 
         m2 = 0x3333333333333333, 
         m3 = 0x0F0F0F0F0F0F0F0F, 
         m4 = 0x000000FF000000FF};

    x = x-((x >> 1)&m1);
    y = y-((y >> 1)&m1);
    u = u-((u >> 1)&m1);
    v = v-((v >> 1)&m1);
    x =(x&m2)+((x >> 2)&m2);
    y =(y&m2)+((y >> 2)&m2);
    u =(u&m2)+((u >> 2)&m2);
    v =(v&m2)+((v >> 2)&m2);
    x = x + y; 
    u = u + v; 
    x =(x&m3)+((x >> 4)&m3);
    u =(u&m3)+((u >> 4)&m3);
    x = x + u; 
    x = x +(x >> 8);
    x = x +(x >> 16);
    x = x&m4; 
    x = x +(x >> 32);
    返回x&0x000001FF;
}

我激动了片刻,但事实证明,即使我在某些测试中未使用inline关键字,gcc也在使用-O3进行内联技巧。当我让gcc发挥作用时,十亿次调用pop4()花费了12.56个千兆字节,但我确定它是将参数折叠为常量表达式。更现实的数字似乎是19.6gc,这又可提高30%的速度。我的测试循环现在看起来像这样,确保每个参数都足够不同以阻止gcc玩花样。

   实时b4 = rdtsc(); 
   for(无符号长i = 10L * 1000 * 1000 * 1000; i <11L * 1000 * 1000 * 1000; ++ i) 
      sum + = pop4(i,i ^ 1,〜i,i | 1); 
   实时e4 = rdtsc(); 

在8.17s内总计有2,560亿个比特流逝。按照16位表查找中的基准,可以计算出3200万位的1.02s。无法直接进行比较,因为另一个工作台没有提供时钟速度,但是看起来我已经在64KB表版本中打败了鼻涕,这首先是对L1缓存的悲剧性使用。

更新:决定做明显的事情,并通过添加四行以上的重复行来创建pop6()。达到22.8gc,在9.5 s的时间内累加了3840亿个比特。因此,现在还有另外20%的数据在800毫秒处达到320亿比特。



28

为什么不迭代除以2?

计数= 0
当n> 0时
  如果(n%2)== 1
    计数+ = 1
  n / = 2  

我同意这不是最快的方法,但是“最佳”方法有些含糊。我认为“最佳”应该有明确的含义


那会起作用并且很容易理解,但是有更快的方法。
马特·豪威尔斯

2
除非你做这LOT,对性能的影响可以忽略不计。因此,在所有事物都平等的情况下,我同意丹尼尔的观点:“最佳”意味着“读起来不像胡言乱语”。

2
为了获得各种方法,我故意没有定义“最佳”。让我们面对现实吧,如果我们跌到这种低级纠缠的水平,我们可能正在寻找一种看起来像黑猩猩打字过的超级快的东西。
马特·豪威尔斯

6
错误的代码。编译器可能会很好用,但是在我的测试中,GCC没有。将(n%2)替换为(n&1);并且比MODULO快得多。将(n / = 2)替换为(n >> = 1); 位移位比除法快得多。
Mecki

6
@Mecki:在我的测试中,gcc(4.0,-O3) 确实做了明显的优化。

26

当您写出位模式时,Hacker's Delight位纠缠变得更加清晰。

unsigned int bitCount(unsigned int x)
{
  x = ((x >> 1) & 0b01010101010101010101010101010101)
     + (x       & 0b01010101010101010101010101010101);
  x = ((x >> 2) & 0b00110011001100110011001100110011)
     + (x       & 0b00110011001100110011001100110011); 
  x = ((x >> 4) & 0b00001111000011110000111100001111)
     + (x       & 0b00001111000011110000111100001111); 
  x = ((x >> 8) & 0b00000000111111110000000011111111)
     + (x       & 0b00000000111111110000000011111111); 
  x = ((x >> 16)& 0b00000000000000001111111111111111)
     + (x       & 0b00000000000000001111111111111111); 
  return x;
}

第一步,将偶数位与奇数位相加,以产生每两个位的总和。其他步骤将高位块添加到低位块,将块大小一直增加一倍,直到最终计数占据整个int为止。


3
该解决方案似乎有较小的问题,与运算符优先级有关。对于每个术语应说:x =(((x >> 1)&0b01010101010101010101010101010101)+(x&0b01010101010101010101010101010101101)); (即添加了额外的括号)。
2014年

21

对于在2 32查找表和逐个循环访问各个位之间的快乐介质:

int bitcount(unsigned int num){
    int count = 0;
    static int nibblebits[] =
        {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
    for(; num != 0; num >>= 4)
        count += nibblebits[num & 0x0f];
    return count;
}

来自http://ctips.pbwiki.com/CountBits


不便携。如果CPU有9位字节怎么办?是的,那里有真正的CPU ...
Robert S. Barnes

15
@Robert S. Barnes,此功能仍然有效。它不假设本地字大小,也完全不引用“字节”。
finnw 2011年

19

可以在中完成O(k),其中k是设置的位数。

int NumberOfSetBits(int n)
{
    int count = 0;

    while (n){
        ++ count;
        n = (n - 1) & n;
    }

    return count;
}

本质上,这是Brian Kernighan的算法(还记得他吗?),但有一点点改变,就是他使用了更简洁的n &= (n-1)形式。
阿德里安·摩尔

17

它不是最快或最好的解决方案,但我以自己的方式发现了同样的问题,并且开始思考和思考。最终我意识到,如果您从数学角度解决问题并绘制图形,则可以这样做,然后发现它是一个具有周期性部分的函数,然后您便会发现周期之间的差...干得好:

unsigned int f(unsigned int x)
{
    switch (x) {
        case 0:
            return 0;
        case 1:
            return 1;
        case 2:
            return 1;
        case 3:
            return 2;
        default:
            return f(x/4) + f(x%4);
    }
}

4
哦,我喜欢。如何回合的Python版本:def f(i, d={0:lambda:0, 1:lambda:1, 2:lambda:1, 3:lambda:2}): return d.get(i, lambda: f(i//4) + f(i%4))()
欠载

10

您要查找的函数通常称为二进制数的“横向和”或“人口数”。Knuth在Fascicle 1A之前的pp11-12中对此进行了讨论(尽管在第2卷4.6.3-(7)中有简短的参考)。

轨迹classicus是彼得·韦格纳的文章“二进制计算机计数问鼎的技术”,从ACM通信,第3卷(1960)5号,322页。他在那里给出了两种不同的算法,一种针对预期为“稀疏”(即数量很少)的数字进行了优化,另一种针对相反的情况进行了优化。


10
  private int get_bits_set(int v)
    {
      int c; // c accumulates the total bits set in v
        for (c = 0; v>0; c++)
        {
            v &= v - 1; // clear the least significant bit set
        }
        return c;
    }

9

几个未解决的问题:

  1. 如果数字为负数呢?
  2. 如果数字为1024,则“迭代除以2”方法将迭代10次。

我们可以修改算法以支持负数,如下所示:

count = 0
while n != 0
if ((n % 2) == 1 || (n % 2) == -1
    count += 1
  n /= 2  
return count

现在要克服第二个问题,我们可以这样写算法:

int bit_count(int num)
{
    int count=0;
    while(num)
    {
        num=(num)&(num-1);
        count++;
    }
    return count;
}

有关完整参考,请参见:

http://goursaha.freeoda.com/Miscellaneous/IntegerBitCount.html


9

我认为Brian Kernighan的方法也将很有用……它会经历与设置位一样多的迭代。因此,如果我们有一个只设置了高位的32位字,那么它将只循环一次。

int countSetBits(unsigned int n) { 
    unsigned int n; // count the number of bits set in n
    unsigned int c; // c accumulates the total bits set in n
    for (c=0;n>0;n=n&(n-1)) c++; 
    return c; 
}

C编程语言第二版,于1988年出版。(由Brian W. Kernighan和Dennis M. Ritchie撰写)在练习2-9中提到了这一点。2006年4月19日,唐·克努斯(Don Knuth)向我指出,这种方法“最初是由彼得·韦格纳(Peter Wegner)在CACM 3(1960),322版中发布的。(还由德里克·莱默(Derrick Lehmer)独立发现,并于1964年由贝肯巴赫(Beckenbach)编辑出版。”


8

我使用以下更直观的代码。

int countSetBits(int n) {
    return !n ? 0 : 1 + countSetBits(n & (n-1));
}

逻辑:n&(n-1)重置n的最后一个设置位。

PS:我知道这不是O(1)解决方案,尽管这是一个有趣的解决方案。


这样做对具有较少位数的“稀疏”数字很有用O(ONE-BITS)。确实是O(1),因为最多有32个1位。
ealfonso

7

“最佳算法”是什么意思?短代码还是禁食代码?您的代码看起来非常优雅,并且执行时间恒定。代码也很短。

但是,如果速度是主要因素,而不是代码大小,那么我认为可以更快:

       static final int[] BIT_COUNT = { 0, 1, 1, ... 256 values with a bitsize of a byte ... };
        static int bitCountOfByte( int value ){
            return BIT_COUNT[ value & 0xFF ];
        }

        static int bitCountOfInt( int value ){
            return bitCountOfByte( value ) 
                 + bitCountOfByte( value >> 8 ) 
                 + bitCountOfByte( value >> 16 ) 
                 + bitCountOfByte( value >> 24 );
        }

我认为对于64位值,这不会更快,但是对于32位值,它会更快。


我的代码有10次操作。您的代码有12个操作。您的链接适用于较小的数组(5)。我使用256个元素。使用缓存可能是个问题。但是,如果您经常使用它,那么这不是问题。
Horcrux08年

事实证明,该方法比位纠结方法要快得多。至于使用更多的内存,它将编译为更少的代码,并且每次您内联函数时都会重复获得收益。因此很容易证明是净赢。

7

我在1990年左右为RISC机器编写了一个快速的位计数宏。它不使用高级算术(乘法,除法,%),内存提取(速度太慢),分支(速度太慢),但是它确实假定CPU具有一个32位桶形移位器(换句话说,>> 1和>> 32占用相同数量的周期。)假设小的常数(例如6、12、24)无需花费任何代价即可加载到寄存器中或进行存储临时使用并反复使用。

基于这些假设,在大多数RISC机器上,它在大约16个周期/指令中计数32位。请注意,15条指令/周期接近周期或指令数的下限,因为似乎至少需要3条指令(掩码,移位,运算符)才能将加数减半,所以log_2(32) = 5、5 x 3 = 15条指令是准下限指令。

#define BitCount(X,Y)           \
                Y = X - ((X >> 1) & 033333333333) - ((X >> 2) & 011111111111); \
                Y = ((Y + (Y >> 3)) & 030707070707); \
                Y =  (Y + (Y >> 6)); \
                Y = (Y + (Y >> 12) + (Y >> 24)) & 077;

这是第一步也是最复杂的一步的秘密:

input output
AB    CD             Note
00    00             = AB
01    01             = AB
10    01             = AB - (A >> 1) & 0x1
11    10             = AB - (A >> 1) & 0x1

因此,如果我采用上面的第一列(A),将其右移1位,然后从AB中减去,则得到输出(CD)。扩展到3位是相似的。您可以根据需要使用上面的类似于我的8行布尔表进行检查。

  • 唐·吉利斯

7

如果您使用的是C ++,则另一种选择是使用模板元编程:

// recursive template to sum bits in an int
template <int BITS>
int countBits(int val) {
        // return the least significant bit plus the result of calling ourselves with
        // .. the shifted value
        return (val & 0x1) + countBits<BITS-1>(val >> 1);
}

// template specialisation to terminate the recursion when there's only one bit left
template<>
int countBits<1>(int val) {
        return val & 0x1;
}

用法是:

// to count bits in a byte/char (this returns 8)
countBits<8>( 255 )

// another byte (this returns 7)
countBits<8>( 254 )

// counting bits in a word/short (this returns 1)
countBits<16>( 256 )

您当然可以进一步扩展此模板以使用不同的类型(甚至自动检测位大小),但是为了清楚起见,我将其简化了。

编辑:忘了说这是很好的,因为它应该在任何C ++编译器中都可以工作,并且如果位值使用常量,它基本上只会为您展开循环(换句话说,我很确定这是最快的通用方法)你会找到)


不幸的是,位计数不是并行进行的,因此它可能会更慢。可能会很好constexpr
imallett

同意-在C ++模板递归中这是一个有趣的练习,但绝对是一个幼稚的解决方案。
pentaphobe

6

我特别喜欢财富文件中的这个示例:

#定义BITCOUNT(x)((((BX_(x)+(BX_(x)>> 4))&0x0F0F0F0F)%255)
#定义BX_(x)((x)-(((x)>> 1)&0x77777777)
                             -((((x)>> 2)&0x33333333)
                             -((((x)>> 3)&0x11111111))

我最喜欢它,因为它是如此漂亮!


1
与其他建议相比,它的效果如何?
asdf

6

Java JDK1.5

Integer.bitCount(n);

其中n是要计算其1的数字。

也检查一下

Integer.highestOneBit(n);
Integer.lowestOneBit(n);
Integer.numberOfLeadingZeros(n);
Integer.numberOfTrailingZeros(n);

//Beginning with the value 1, rotate left 16 times
     n = 1;
         for (int i = 0; i < 16; i++) {
            n = Integer.rotateLeft(n, 1);
            System.out.println(n);
         }

并不是真正的算法,这只是一个库调用。对于Java有用,对其他人则没有那么多。
benzado 2010年

2
@benzado是正确的,但无论如何还是+1,因为一些Java开发人员可能不知道该方法
finnw 2011年

@finnw,我是那些开发人员之一。:)
neevek

6

我发现使用SIMD指令(SSSE3和AVX2)在数组中实现了位计数。与使用__popcnt64内在函数相比,它的性能要好2-2.5倍。

SSSE3版本:

#include <smmintrin.h>
#include <stdint.h>

const __m128i Z = _mm_set1_epi8(0x0);
const __m128i F = _mm_set1_epi8(0xF);
//Vector with pre-calculated bit count:
const __m128i T = _mm_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);

uint64_t BitCount(const uint8_t * src, size_t size)
{
    __m128i _sum =  _mm128_setzero_si128();
    for (size_t i = 0; i < size; i += 16)
    {
        //load 16-byte vector
        __m128i _src = _mm_loadu_si128((__m128i*)(src + i));
        //get low 4 bit for every byte in vector
        __m128i lo = _mm_and_si128(_src, F);
        //sum precalculated value from T
        _sum = _mm_add_epi64(_sum, _mm_sad_epu8(Z, _mm_shuffle_epi8(T, lo)));
        //get high 4 bit for every byte in vector
        __m128i hi = _mm_and_si128(_mm_srli_epi16(_src, 4), F);
        //sum precalculated value from T
        _sum = _mm_add_epi64(_sum, _mm_sad_epu8(Z, _mm_shuffle_epi8(T, hi)));
    }
    uint64_t sum[2];
    _mm_storeu_si128((__m128i*)sum, _sum);
    return sum[0] + sum[1];
}

AVX2版本:

#include <immintrin.h>
#include <stdint.h>

const __m256i Z = _mm256_set1_epi8(0x0);
const __m256i F = _mm256_set1_epi8(0xF);
//Vector with pre-calculated bit count:
const __m256i T = _mm256_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 
                                   0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);

uint64_t BitCount(const uint8_t * src, size_t size)
{
    __m256i _sum =  _mm256_setzero_si256();
    for (size_t i = 0; i < size; i += 32)
    {
        //load 32-byte vector
        __m256i _src = _mm256_loadu_si256((__m256i*)(src + i));
        //get low 4 bit for every byte in vector
        __m256i lo = _mm256_and_si256(_src, F);
        //sum precalculated value from T
        _sum = _mm256_add_epi64(_sum, _mm256_sad_epu8(Z, _mm256_shuffle_epi8(T, lo)));
        //get high 4 bit for every byte in vector
        __m256i hi = _mm256_and_si256(_mm256_srli_epi16(_src, 4), F);
        //sum precalculated value from T
        _sum = _mm256_add_epi64(_sum, _mm256_sad_epu8(Z, _mm256_shuffle_epi8(T, hi)));
    }
    uint64_t sum[4];
    _mm256_storeu_si256((__m256i*)sum, _sum);
    return sum[0] + sum[1] + sum[2] + sum[3];
}

6

我一直在竞争性编程中使用它,它易于编写且高效:

#include <bits/stdc++.h>

using namespace std;

int countOnes(int n) {
    bitset<32> b(n);
    return b.count();
}

5

有很多算法可以对设置的位进行计数。但是我认为最好的是更快的!您可以在此页面上看到详细信息:

位扭曲的黑客

我建议这个:

使用64位指令对14、24或32位字中设置的位进行计数

unsigned int v; // count the number of bits set in v
unsigned int c; // c accumulates the total bits set in v

// option 1, for at most 14-bit values in v:
c = (v * 0x200040008001ULL & 0x111111111111111ULL) % 0xf;

// option 2, for at most 24-bit values in v:
c =  ((v & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;
c += (((v & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL) 
     % 0x1f;

// option 3, for at most 32-bit values in v:
c =  ((v & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;
c += (((v & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL) % 
     0x1f;
c += ((v >> 24) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;

此方法需要具有快速模数划分的64位CPU才能高效。第一个选项仅需执行3个操作;第二个选项需要10;第三个选项需要15


5

快速的C#解决方案,使用预先计算的字节位数表并在输入大小上进行分支。

public static class BitCount
{
    public static uint GetSetBitsCount(uint n)
    {
        var counts = BYTE_BIT_COUNTS;
        return n <= 0xff ? counts[n]
             : n <= 0xffff ? counts[n & 0xff] + counts[n >> 8]
             : n <= 0xffffff ? counts[n & 0xff] + counts[(n >> 8) & 0xff] + counts[(n >> 16) & 0xff]
             : counts[n & 0xff] + counts[(n >> 8) & 0xff] + counts[(n >> 16) & 0xff] + counts[(n >> 24) & 0xff];
    }

    public static readonly uint[] BYTE_BIT_COUNTS = 
    {
        0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
    };
}

具有讽刺意味的是,该表可能是由该线程中发布的任何算法创建的!但是,使用这样的表意味着持续的性能。因此,更进一步并创建64K转换表将使所需的AND,SHIFT和ADD操作减半。位操纵器一个有趣的主题!
user924272'1

由于缓存问题,较大的表可能会变慢(而不是恒定时间)。您可以一次“查找” 3位(0xe994 >>(k*2))&3,而无需访问内存...
greggo

5

这是一个便携式模块(ANSI-C),可以在任何体系结构上对每种算法进行基准测试。

您的CPU有9位字节?没问题:-)目前,它实现了2种算法,即K&R算法和按字节查找表。查找表平均比K&R算法快3倍。如果有人想出一种使“ Hacker's Delight”算法可移植的方法可以随意添加。

#ifndef _BITCOUNT_H_
#define _BITCOUNT_H_

/* Return the Hamming Wieght of val, i.e. the number of 'on' bits. */
int bitcount( unsigned int );

/* List of available bitcount algorithms.  
 * onTheFly:    Calculate the bitcount on demand.
 *
 * lookupTalbe: Uses a small lookup table to determine the bitcount.  This
 * method is on average 3 times as fast as onTheFly, but incurs a small
 * upfront cost to initialize the lookup table on the first call.
 *
 * strategyCount is just a placeholder. 
 */
enum strategy { onTheFly, lookupTable, strategyCount };

/* String represenations of the algorithm names */
extern const char *strategyNames[];

/* Choose which bitcount algorithm to use. */
void setStrategy( enum strategy );

#endif

#include <limits.h>

#include "bitcount.h"

/* The number of entries needed in the table is equal to the number of unique
 * values a char can represent which is always UCHAR_MAX + 1*/
static unsigned char _bitCountTable[UCHAR_MAX + 1];
static unsigned int _lookupTableInitialized = 0;

static int _defaultBitCount( unsigned int val ) {
    int count;

    /* Starting with:
     * 1100 - 1 == 1011,  1100 & 1011 == 1000
     * 1000 - 1 == 0111,  1000 & 0111 == 0000
     */
    for ( count = 0; val; ++count )
        val &= val - 1;

    return count;
}

/* Looks up each byte of the integer in a lookup table.
 *
 * The first time the function is called it initializes the lookup table.
 */
static int _tableBitCount( unsigned int val ) {
    int bCount = 0;

    if ( !_lookupTableInitialized ) {
        unsigned int i;
        for ( i = 0; i != UCHAR_MAX + 1; ++i )
            _bitCountTable[i] =
                ( unsigned char )_defaultBitCount( i );

        _lookupTableInitialized = 1;
    }

    for ( ; val; val >>= CHAR_BIT )
        bCount += _bitCountTable[val & UCHAR_MAX];

    return bCount;
}

static int ( *_bitcount ) ( unsigned int ) = _defaultBitCount;

const char *strategyNames[] = { "onTheFly", "lookupTable" };

void setStrategy( enum strategy s ) {
    switch ( s ) {
    case onTheFly:
        _bitcount = _defaultBitCount;
        break;
    case lookupTable:
        _bitcount = _tableBitCount;
        break;
    case strategyCount:
        break;
    }
}

/* Just a forwarding function which will call whichever version of the
 * algorithm has been selected by the client 
 */
int bitcount( unsigned int val ) {
    return _bitcount( val );
}

#ifdef _BITCOUNT_EXE_

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

/* Use the same sequence of pseudo random numbers to benmark each Hamming
 * Weight algorithm.
 */
void benchmark( int reps ) {
    clock_t start, stop;
    int i, j;
    static const int iterations = 1000000;

    for ( j = 0; j != strategyCount; ++j ) {
        setStrategy( j );

        srand( 257 );

        start = clock(  );

        for ( i = 0; i != reps * iterations; ++i )
            bitcount( rand(  ) );

        stop = clock(  );

        printf
            ( "\n\t%d psudoe-random integers using %s: %f seconds\n\n",
              reps * iterations, strategyNames[j],
              ( double )( stop - start ) / CLOCKS_PER_SEC );
    }
}

int main( void ) {
    int option;

    while ( 1 ) {
        printf( "Menu Options\n"
            "\t1.\tPrint the Hamming Weight of an Integer\n"
            "\t2.\tBenchmark Hamming Weight implementations\n"
            "\t3.\tExit ( or cntl-d )\n\n\t" );

        if ( scanf( "%d", &option ) == EOF )
            break;

        switch ( option ) {
        case 1:
            printf( "Please enter the integer: " );
            if ( scanf( "%d", &option ) != EOF )
                printf
                    ( "The Hamming Weight of %d ( 0x%X ) is %d\n\n",
                      option, option, bitcount( option ) );
            break;
        case 2:
            printf
                ( "Please select number of reps ( in millions ): " );
            if ( scanf( "%d", &option ) != EOF )
                benchmark( option );
            break;
        case 3:
            goto EXIT;
            break;
        default:
            printf( "Invalid option\n" );
        }

    }

 EXIT:
    printf( "\n" );

    return 0;
}

#endif

1
我非常喜欢您的插件,多态方法以及将其构建为可重用库或独立的测试可执行文件的开关。很好的想法=)

5

你能做的是

while(n){
    n=n&(n-1);
    count++;
}

其背后的逻辑是n-1的位与n的最右置位相反。如果n = 6,即110,则5为101,则这些位从n的最右置位开始反转。因此,如果我们和这两个变量一起使用,我们将在每次迭代中将最右边的位设为0,并始终转到下一个最右边的设置位,因此,对设置位进行计数。当设置每个位时,最差的时间复杂度将是O(logn)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.