【问题标题】:Count number of 1's in binary format of decimal number以十进制数的二进制格式计算 1 的个数
【发布时间】:2016-01-22 08:31:44
【问题描述】:

我试图找出一个大十进制数的二进制形式的 1 的个数(十进制数可以大到 1000000)。

我试过这段代码:

while(sum>0)  
{  
    if(sum%2 != 0)  
    {  
        c++;   // counting number of ones  
    }  
    sum=sum/2;  
}  

我想要一个更快的算法,因为大十进制输入需要很长时间。请建议我一个有效的算法。

【问题讨论】:

  • 1000000 不是很大。你为什么不转换成一个字符串并数个数呢?
  • 真的吗? 20 次迭代需要很长时间?
  • @Rapptz,我希望你的意思是不要重新发明轮子并使用 std::count :)
  • 这是一个汉明权重问题:en.wikipedia.org/wiki/Hamming_weight。阅读它,您可能会了解如何滥用二元运算符:-)
  • gurmeet.net/puzzles/fast-bit-counting-routines 几个很酷的简单算法。根据场景,有些比其他更好。

标签: c++ binary decimal


【解决方案1】:

您正在寻找的是“popcount”,它在更高版本的 x64 CPU 上实现为单个 CPU 指令,在速度上不会被击败:

#ifdef __APPLE__
#define NAME(name) _##name
#else
#define NAME(name) name
#endif

/*
 * Count the number of bits set in the bitboard.
 *
 * %rdi: bb
 */
.globl NAME(cpuPopcount);
NAME(cpuPopcount):
    popcnt %rdi, %rax
    ret

当然,您需要先测试 CPU 是否支持它:

/*
 * Test if the CPU has the popcnt instruction.
 */
.globl NAME(cpuHasPopcount);
NAME(cpuHasPopcount):
    pushq %rbx

    movl $1, %eax
    cpuid                   // ecx=feature info 1, edx=feature info 2

    xorl %eax, %eax

    testl $1 << 23, %ecx
    jz 1f
    movl $1, %eax

1:
    popq %rbx
    ret

这是 C 中的一个实现:

unsigned cppPopcount(unsigned bb)
{
#define C55 0x5555555555555555ULL
#define C33 0x3333333333333333ULL
#define C0F 0x0f0f0f0f0f0f0f0fULL
#define C01 0x0101010101010101ULL

    bb -= (bb >> 1) & C55;              // put count of each 2 bits into those 2 bits
    bb = (bb & C33) + ((bb >> 2) & C33);// put count of each 4 bits into those 4 bits
    bb = (bb + (bb >> 4)) & C0F;        // put count of each 8 bits into those 8 bits
    return (bb * C01) >> 56;            // returns left 8 bits of x + (x<<8) + (x<<16) + (x<<24) + ...
}

GNU C 编译器运行时包含一个“内置”,它可能比上面的实现更快(它可能使用 CPU popcnt 指令,但这是特定于实现的):

unsigned builtinPopcount(unsigned bb)
{
    return __builtin_popcountll(bb);
}

所有上述实现都在我的 C++ 国际象棋库中使用,因为当使用位板表示棋子位置时,popcount 在国际象棋移动生成中起着至关重要的作用。我使用函数指针,在库初始化期间设置,指向用户请求的实现,然后通过该指针使用 popcount 函数。

Google 将提供许多其他实现,因为这是一个有趣的问题,例如:http://wiki.cs.pdx.edu/forge/popcount.html

【讨论】:

    【解决方案2】:

    在 C++ 中,您可以这样做。

    #include <bitset>
    #include <iostream>
    #include <climits>
    
    size_t popcount(size_t n) {
        std::bitset<sizeof(size_t) * CHAR_BIT> b(n);
        return b.count();
    }
    
    int main() {
        std::cout << popcount(1000000);
    }
    

    【讨论】:

    • 为什么假设为 32 位? size_t 不一定是 32 位的。而且 1 个字节并不一定意味着 8 位。因此,我编辑了答案。
    • @Nawaz 回答说“高达 100 万”,所以我认为 32 位就足够了。不过我很欣赏编辑。
    • 哦,嘿,我实际上是在 C++ 谷歌搜索后偶然发现了这个答案。嗨,丹尼!
    • 知道如何以 constexpr 的方式进行操作吗?
    【解决方案3】:

    有很多方法。 Brian Kernighan's way 易于理解且速度相当快:

    unsigned int v = value(); // 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
    }
    

    【讨论】:

    • 赞成这个,因为它非常漂亮和整洁。仍然比大多数 popcount 实现需要更多的迭代:-)
    • 我不明白。向我解释一下:for (c = 0; v; c++)v 未初始化。
    • @Nawaz:从第一行的注释来看,v 应该包含我们要计算其位的值。
    • @MikeSeymour:哦。谢谢。我编辑了答案以使其更好。
    • @Nawaz 谢谢。我认为评论就足够了:)
    【解决方案4】:

    使用右移位运算符

        int number = 15; // this is input number
        int oneCount = number & 1 ? 1 : 0;
        while(number = number >> 1)
        {
            if(number & 1)
                ++oneCount;
        }
    
        cout << "# of ones :"<< oneCount << endl;
    

    【讨论】:

    • 如果数字为负数会怎样?
    【解决方案5】:
    int count_1s_in_Num(int num)
    {
        int count=0;
        while(num!=0)
        {
            num = num & (num-1);
            count++;
        }
        return count;
    }
    

    如果对整数和减法结果应用 AND 运算,则结果是一个与原始整数相同的新数字,只是最右边的 1 现在是 0。例如,01110000 AND (01110000 – 1) = 01110000 和 01101111 = 01100000。

    此解的运行时间为 O(m),其中 m 为解中 1 的个数。

    【讨论】:

      猜你喜欢
      • 2012-02-10
      • 1970-01-01
      • 2021-12-27
      • 2019-04-29
      • 2021-03-05
      • 1970-01-01
      • 2019-03-07
      • 2019-09-22
      • 2017-06-05
      相关资源
      最近更新 更多