【问题标题】:How to move all set bits to the right?如何将所有设置位向右移动?
【发布时间】:2013-04-14 20:52:15
【问题描述】:

问题:如何开发算法将32位整数中的所有设置位(1)向右移动

例子

V= 0b01001000110010 

V 包含 5 个设置位,如果我们将它们向右移动,我们会得到:

V = 0b011111

我尝试过的:

v = v - ((v >> 1) & 0x55555555);                    // reuse input as temporary
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);     // temp
c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; // count

以上代码以 32 位整数形式返回设置的位数

并使用以下代码

c = (1<<c) - 1;

我们会将c 的第一位设置为1Explaination

还有比上述方案更好的算法吗?

是否可以在建议的解决方案中仅使用按位运算(&amp;|^~&gt;&gt;&lt;&lt;)?

【问题讨论】:

  • 你用这个做什么?你想计算 1 位吗?

标签: c++ c bit-manipulation


【解决方案1】:
bitset<32> const bv( V );
size_t const numSetBits = bv.count();
uint32_t const answer = ~( ~0U << numSetBits );

【讨论】:

  • 这是一个很好的通用解决方案。如果好奇,OP 可以在他的标准库中查看bitset::count 的实现。另外:如果count() 是您使用它的全部,您不妨将bitset 设为常量。我认为(1 &lt;&lt; numSetBits) - 1 也更清晰。
  • 请注意,如果输入设置了所有位,则此操作将失败(如此大的移动量是未定义的行为)。
  • @MarcGlisse 你能解释一下为什么它是未定义的行为吗
  • 因为它在标准中这么说(因为对于 32 位类型,某些处理器会将 a
  • @MOHAMED:谢谢。 Marc Glisse:您能提供一些关于未定义行为情况的参考吗?
【解决方案2】:

我看到的最佳解决方案是使用人口计数来获取 1 位的数量,然后只需使用 (1 &lt;&lt; (pop_cnt % typeSize)) - 1[1],这就是您已经在做的事情。

要查找人口计数,您可以查看here,有几种有效的实现。如果你对 gcc 没问题,那么 gcc 的内置 __builtin_popcount 指令应该会为你的硬件生成最有效的代码。

[1] 正如另一篇文章的 cmets 中正确指出的那样,1 &lt;&lt; pop_cnt 是未定义的行为,如果单词中的所有位都设置为 1。为了避免这种情况,请预先应用模字大小。只要您使用无符号类型,这将避免未定义的行为,为您提供正确的结果,并且如果您使用字长数据类型,则实际上应该在 x86 下生成完全相同的代码。

【讨论】:

    【解决方案3】:

    您可以使用V &gt;&gt;= 1;int v = (V &gt;&gt; 1);,不是吗?

    如果意图是将所有非零位向右移动 all,那么您可以这样做...

    int v = 0;
    int i;
    
    for( i = 0; i < 32; i++, V >>= 1 )
       if( (V & 1) != 0 )
          v = (v << 1) + 1;
    
    V = v;
    

    【讨论】:

    • 这不会从0b01001000110010 产生0b011111
    • 嗯...所以目的是将所有 1 位一直向右移动?我没有那样读这个问题,坦率地说,我现在不相信。
    • 已编辑以允许将所有 1 位一直向右移动
    【解决方案4】:

    这将获得一些价值v

    // Count the bits (only iterates once per set bit)
    unsigned count = 0;
    for(; v!=0; v&=(v-1))
        ++count;
    
    // Produce that many "shifted bits".
    v = (1 << count) - 1;
    

    【讨论】:

      【解决方案5】:

      我建议使用两个变量:

      unsigned int Squish_Bits(unsigned int value)
      {
        unsigned int result = 0;
        for (i = 0; i < sizeof(value)/CHAR_BITS; ++i)
        {
          if (value & (1 << i))
          {
             result <<= 1;
             result |= 1;
          }
        }
        return result;
      }
      

      虽然此函数不计算位,但它确实解决了您的示例。

      这在汇编语言中会更好地实现,因为您可以将位移入进位,然后将进位移入结果。

      【讨论】:

        【解决方案6】:
        int v = 0b01001000110010;
        int a = 0;
        
        for (int i = 0; i < 32; ++i) {
            if((v & 1) == 1)
               a = (a << 1) + 1;
        
            v = v >> 1;
        }
        
        v = a;
        

        【讨论】:

          【解决方案7】:

          您可以尝试找到一个魔术常数并使用乘法和 (32 - 5) 右移(假设您使用 32 位整数来存储常数)。

          #include <iostream>
          
          int main()
          {
             unsigned V = 0b01001000110010; 
             unsigned magic = 0x79838cb2; // try some random numbers until you succeed
             unsigned result = (V * magic) >> (32 - 5);   
          
             std::cout << result; // writes 31
          }
          

          看看这个question如何找到这样的常量

          【讨论】:

          • V 不是常数。我正在寻找常见的算法。输入是 v,输出是右移的设置位
          • @MOHAMED 好的,那么您可以使用 Arun Saha 的解决方案。我将把这个答案留给需要恒定 V 的人将来参考。
          【解决方案8】:
          int shift(int V) {
              if (V != 0) {
                  int r = 1;
                  for (int i = 0; i < 32; i++)
                      if ((V >> i) & 1) r <<= 1;
                  return r - 1;
              } else return 0;
          }
          

          保存一些添加。

          【讨论】:

            猜你喜欢
            • 2021-07-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-02-19
            • 2020-07-10
            • 1970-01-01
            • 1970-01-01
            • 2022-11-17
            相关资源
            最近更新 更多