【问题标题】:Number of unset bit left of most significant set bit?最高有效设置位左侧未设置位的数量?
【发布时间】:2011-05-05 01:53:39
【问题描述】:

假设 64 位整数 0x000000000000FFFF 将表示为

00000000 00000000  00000000 00000000
00000000 00000000 >11111111 11111111

如何找到最高有效设置位(标有 >)左侧的未设置位数量?

【问题讨论】:

  • 您对 C、C# 或 C++ 感兴趣吗?理论相同,但语言不同。
  • 既然我认为有一些小技巧可以做到这一点,而且它在所有语言中看起来都差不多,这并不重要。
  • 谷歌“fxtbook.pdf”,第 1.6.1 章
  • 如果此值始终为 0*1* 模式(全 0,后跟全 1),您可以采用多种捷径,是这样吗?
  • @jkerian 是的......那么它只是 64 - 位计数(值)。搜索 popcount 或 Debruijn 以获取有效的位数计数方法。

标签: c# c++ c 64-bit bit-manipulation


【解决方案1】:

在直接 C 中(我的设置中 long long 是 64 位),取自类似的 Java 实现:(在阅读更多汉明权重后更新)

更多解释:顶部只是将所有位设置到最高有效 1 的右侧,然后将其取反。 (即,最重要的 1 的“左边”的所有 0 现在都是 1,其他的都是 0)。

然后我使用Hamming Weight 实现来计算位数。

unsigned long long i = 0x0000000000000000LLU;

i |= i >> 1;
i |= i >> 2;
i |= i >> 4;
i |= i >> 8;
i |= i >> 16;
i |= i >> 32;
// Highest bit in input and all lower bits are now set. Invert to set the bits to count.
i=~i;

i -= (i >> 1) & 0x5555555555555555LLU; // each 2 bits now contains a count
i = (i & 0x3333333333333333LLU) + ((i >> 2) & 0x3333333333333333LLU); // each 4 bits now contains a count
i = (i + (i >> 4)) & 0x0f0f0f0f0f0f0f0fLLU; // each 8 bits now contains a count 
i *= 0x0101010101010101LLU; // add each byte to all the bytes above it
i >>= 56; // the number of bits

printf("Leading 0's = %lld\n", i);

我很想知道这是如何提高效率的。虽然用几个值对其进行了测试,但它似乎可以工作。

【讨论】:

    【解决方案2】:

    基于:http://www.hackersdelight.org/HDcode/nlz.c.txt

    template<typename T> int clz(T v) {int n=sizeof(T)*8;int c=n;while (n){n>>=1;if (v>>n) c-=n,v>>=n;}return c-v;}
    

    如果您想要一个可以让您减少午餐的版本,请点击此处:

    int clz(uint64_t v) {
        int n=64,c=64;
        while (n) {
            n>>=1;
            if (v>>n) c-=n,v>>=n;
        }
        return c-v;
    }
    

    正如您将看到的,您可以通过仔细分析汇编程序来节省周期,但这里的策略并不是一个糟糕的策略。 while 循环将运行 Lg[64]=6 次;每次它将问题转换为计算大小为一半的整数上的前导位数之一。 while 循环中的 if 语句提出了一个问题:“我可以将这个整数表示为一半的位数吗”,或者类似地,“如果我把它切成两半,我会丢失它吗?”。在 if() 有效负载完成后,我们的数字将始终位于最低 n 位。 在最后阶段,v 要么为 0,要么为 1,这样就正确地完成了计算。

    【讨论】:

      【解决方案3】:

      如果你正在处理无符号整数,你可以这样做:

      #include <math.h>
      int numunset(uint64_t number)
      {
          int nbits = sizeof(uint64_t)*8;
          if(number == 0)
              return nbits;
          int first_set = floor(log2(number));
          return nbits - first_set - 1;
      }
      

      我不知道它与已经提供的循环和计数方法的性能相比如何,因为 log2() 可能很昂贵。

      编辑

      这可能会导致一些高值整数问题,因为log2() 函数正在转换为double,并且可能会出现一些数字问题。您可以使用与long double 一起使用的log2l() 函数。更好的解决方案是使用整数log2() 函数,如this question

      【讨论】:

      • 哦,是的log2 太贵了!我什至完全忘记了这种可能性。我不知道这些东西是如何在处理器 FPU 中实现的,但是计算任何非算术函数通常会导致计算一些级数和。我相信这样的事情会占用大量的 CPU 周期。
      【解决方案4】:
      // clear all bits except the lowest set bit
      x &= -x;     
      
      // if x==0, add 0, otherwise add x - 1. 
      // This sets all bits below the one set above to 1.
      x+= (-(x==0))&(x - 1);
      
      return 64 - count_bits_set(x);
      

      count_bits_set 是您能找到的最快的位计数版本。有关各种位计数技术,请参阅 https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel

      【讨论】:

      • 就目前而言,第一行不是清除所有位,除了 最低 设置一个吗?
      • @JeppeStigNielsen,啊,确实如此!回想起来,我不知道为什么我会这样回答。
      • -1 这是如何被接受的?这是完全错误的。首先,它尝试计算高于最低位集的位位置数,这不是所要求的。其次,第二行的条件是倒退的。前两行想要的效果可以用if (x) x ^= x-1...但是只要做测试,还不如做if (!x) return ...,然后0可以映射成任何东西。 (更好的是,将此函数设为未定义为 0 并让调用者处理它。)
      【解决方案5】:

      我不确定我是否正确理解了这个问题。我认为您有一个 64 位的值,并希望在其中找到前导零的数量。

      一种方法是找到最高有效位并简单地从 63 中减去它的位置(假设最低位是位 0)。您可以通过测试是否在所有 64 位的循环中设置了位来找出最重要的位。

      另一种方法可能是在 gcc 中使用(非标准)__builtin_clz

      【讨论】:

        【解决方案6】:

        我同意二进制搜索的想法。然而,这里有两点很重要:

        1. 您问题的有效答案范围是从 0 到 64包含。换句话说 - 这个问题可能有 65 个不同的答案。我认为(几乎可以肯定)所有发布“二进制搜索”解决方案的人都错过了这一点,因此他们会得到错误的答案,无论是零还是 MSB 位打开的数字。
        2. 如果速度很关键 - 您可能希望避免循环。有一种优雅的方式可以使用模板来实现这一点。

        以下模板内容可以正确找到任何 unsigned 类型变量的 MSB。

        // helper
        template <int bits, typename T>
        bool IsBitReached(T x)
        {
            const T cmp = T(1) << (bits ? (bits-1) : 0);
            return (x >= cmp);
        }
        
        template <int bits, typename T>
        int FindMsbInternal(T x)
        {
            if (!bits)
                return 0;
        
            int ret;
            if (IsBitReached<bits>(x))
            {
                ret = bits;
                x >>= bits;
            } else
                ret = 0;
        
            return ret + FindMsbInternal<bits/2, T>(x);
        }
        
        // Main routine
        template <typename T>
        int FindMsb(T x)
        {
            const int bits = sizeof(T) * 8;
            if (IsBitReached<bits>(x))
                return bits;
        
            return FindMsbInternal<bits/2>(x);
        }
        

        【讨论】:

          【解决方案7】:

          给你,根据需要更新其他尺寸非常简单......

          int bits_left(unsigned long long value)
          {
            static unsigned long long mask = 0x8000000000000000;
            int c = 64;
            // doh
            if (value == 0)
              return c;
          
            // check byte by byte to see what has been set
            if (value & 0xFF00000000000000)
              c = 0;
            else if (value & 0x00FF000000000000)
              c = 8;
            else if (value & 0x0000FF0000000000)
              c = 16;
            else if (value & 0x000000FF00000000)
              c = 24;
            else if (value & 0x00000000FF000000)
              c = 32;
            else if (value & 0x0000000000FF0000)
              c = 40;
            else if (value & 0x000000000000FF00)
              c = 48;
            else if (value & 0x00000000000000FF)
              c = 56;
          
            // skip
            value <<= c;
          
            while(!(value & mask))
            {
              value <<= 1;
              c++;
            }
          
            return c;
          }
          

          【讨论】:

            【解决方案8】:

            user470379's 的想法相同,但倒计时...
            假设所有 64 位都未设置。当值大于 0 时,继续向右移动值并减少未设置的位数:

            /* untested */
            int countunsetbits(uint64_t val) {
                int x = 64;
                while (val) { x--; val >>= 1; }
                return x;
            }
            

            【讨论】:

            • 请不要这样做。这个 while() 循环将执行 64 次。您可以在 6 次循环迭代中执行此操作,因为您可以对问题进行二进制分区。查看我的答案,基于 Hacker's Delight 实现。
            【解决方案9】:

            试试

            int countBits(int value)
            {
                int result = sizeof(value) * CHAR_BITS;  // should be 64
            
                while(value != 0)
                {
                    --result;
                    value = value >> 1; // Remove bottom bits until all 1 are gone.
                }
                return result;
            }
            

            【讨论】:

              【解决方案10】:

              使用以 2 为底的对数得到最重要的数字,即 1。

              log(2) = 1, meaning 0b10 -> 1
              log(4) = 2, 5-7 => 2.xx, or 0b100 -> 2
              log(8) = 3, 9-15 => 3.xx, 0b1000 -> 3
              log(16) = 4 you get the idea
              

              等等…… 中间的数字成为日志结果的分数。因此,将值类型转换为 int 会为您提供最重要的数字。

              一旦你得到这个数字,比如说 b,简单的 64 - n 就是答案。

              function get_pos_msd(int n){
                  return int(log2(n))
              }
              
              last_zero = 64 - get_pos_msd(n)
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2015-09-22
                • 2010-10-19
                • 1970-01-01
                • 1970-01-01
                • 2017-11-18
                • 1970-01-01
                • 1970-01-01
                • 2011-08-26
                相关资源
                最近更新 更多