【问题标题】:Find the highest order bit in C [duplicate]在C中找到最高位[重复]
【发布时间】:2010-09-08 08:45:29
【问题描述】:

我所追求的是我可以输入一个数字的东西,它会返回最高位。我相信有一个简单的方法。下面是一个示例输出(左边是输入)

1 -> 1
2 -> 2
3 -> 2
4 -> 4
5 -> 4
6 -> 4
7 -> 4
8 -> 8
9 -> 8
...
63 -> 32

【问题讨论】:

    标签: c


    【解决方案1】:

    我非常喜欢的最佳算法是:

    unsigned hibit(unsigned n) {
        n |= (n >>  1u);
        n |= (n >>  2u);
        n |= (n >>  4u);
        n |= (n >>  8u);
        n |= (n >> 16u);
        return n - (n >> 1);
    }
    

    而且它很容易像这样扩展为 uint64_t:

    uint64_t hibit(uint64_t n) {
        n |= (n >>  1u);
        n |= (n >>  2u);
        n |= (n >>  4u);
        n |= (n >>  8u);
        n |= (n >> 16u);
        n |= (n >> 32u);
        return n - (n >> 1);
    }
    

    甚至到 __int128

    __int128 hibit(__int128 n) {
        n |= (n >>  1u);
        n |= (n >>  2u);
        n |= (n >>  4u);
        n |= (n >>  8u);
        n |= (n >> 16u);
        n |= (n >> 32u);
        n |= (n >> 64u);
        return n - (n >> 1);
    }
    

    另外是跨平台解决方案独立于使用编译器

    【讨论】:

      【解决方案2】:

      这次聚会有点晚了,但我找到的最简单的解决方案是:将现代 GCC 作为编译器:

      static inline int_t get_msb32 (register unsigned int val)
      {
        return 32 - __builtin_clz(val);
      }
      
      static inline int get_msb64 (register unsigned long long val)
      {
        return 64 - __builtin_clzll(val);
      }
      

      它甚至是相对可移植的(至少它可以在任何 GCC 平台上运行)。

      【讨论】:

      • clang也支持。
      【解决方案3】:

      Linux 内核有许多像这样方便的位图,以最有效的方式编码,适用于多种架构。您可以在include/asm-generic/bitops/fls.h(和朋友)中找到通用版本,但如果速度至关重要,而可移植性不是,请参阅include/asm-x86/bitops.h 了解使用内联汇编的定义。

      【讨论】:

      • Linux 内核在 bitops 中充满了未定义的行为。我不建议任何人使用它(或复制/粘贴例程),除非他们知道自己在做什么或对该功能进行完整的自测。
      【解决方案4】:

      如果您不需要可移植的解决方案并且您的代码在 x86 兼容的 CPU 上执行,您可以使用 Microsoft Visual C/C++ 编译器提供的 _BitScanReverse() 内部函数。它映射到返回最高位集合的 BSR CPU 指令。

      【讨论】:

        【解决方案5】:

        我想出的一个很好的解决方案是对位进行二进制搜索。

        uint64_t highestBit(uint64_t a, uint64_t bit_min, uint64_t bit_max, uint16_t bit_shift){
            if(a == 0) return 0;
            if(bit_min >= bit_max){
                if((a & bit_min) != 0)
                    return bit_min;
                return 0;
            }
            uint64_t bit_mid = bit_max >> bit_shift;
            bit_shift >>= 1;
            if((a >= bit_mid) && (a < (bit_mid << 1)))
                return bit_mid;
            else if(a > bit_mid)
                return highestBit(a, bit_mid, bit_max, bit_shift);
            else
                return highestBit(a, bit_min, bit_mid, bit_shift);
        
        }
        

        最大位是 2 的最高幂,因此对于 64 位数字,它将是 2^63。位移应该初始化为位数的一半,因此对于 64 位,它将是 32。

        【讨论】:

          【解决方案6】:

          这可以通过现有的库调用轻松解决。

          int highestBit(int v){
            return fls(v) << 1;
          }
          

          Linux 手册页提供了有关此函数及其对应输入类型的更多详细信息。

          【讨论】:

          • 其实ffs返回的是最低位。
          • @Soren 更好,使用 (8*sizeof(long long) - __builtin_clzll(v))
          • @Ragnar,如果你这样做,请使用(CHAR_BIT*sizeof(long long) - __builtin_clzll(v),但我不认为这个公式足够便携,不需要它。 &lt;limits.h&gt;
          • 这不是倒退吗?应该是1UL &lt;&lt; fls(v)
          • 在 Ubuntu 20.04 上没有这方面的手册页,而且它似乎不在 glibc 中。据我所知,这是 FreeBSD 的一项发明。
          【解决方案7】:

          fls 在许多体系结构上都达到了硬件指令的最低点。我怀疑这可能是最简单、最快的方法。

          1<<(fls(input)-1)
          

          【讨论】:

          • 显然是最佳答案。我想知道为什么所有那些喜欢bit twiddling hacks的网站,他们如此专注于减少此类问题的操作数量,甚至不提这个。
          • 如果没有设置位会发生什么?一个负数左移怎么算?我相信这是未定义的行为。
          • @jww 没错。 fls(0) -> 0,留下 &lt;&lt; 的 rhs 为负数,未定义。在我的辩护中,这在问题空间中没有说明;-)
          • 这应该是公认的答案。我很高兴我一直在滚动!
          • @krs013 - 我不知道。如果没有记错,它在 POSIX 之外不可用。
          【解决方案8】:
          // Note doesn't cover the case of 0 (0 returns 1)
          inline unsigned int hibit( unsigned int x )
          {
            unsigned int log2Val = 0 ;
            while( x>>=1 ) log2Val++;  // eg x=63 (111111), log2Val=5
            return 1 << log2Val ; // finds 2^5=32
          }
          

          【讨论】:

            【解决方案9】:

            一种快速的方法是通过查找表。对于 32 位输入和 8 位查找表,in 只需要 4 次迭代:

            int highest_order_bit(int x)
            {
                static const int msb_lut[256] =
                    {
                        0, 0, 1, 1, 2, 2, 2, 2, // 0000_0000 - 0000_0111
                        3, 3, 3, 3, 3, 3, 3, 3, // 0000_1000 - 0000_1111
                        4, 4, 4, 4, 4, 4, 4, 4, // 0001_0000 - 0001_0111
                        4, 4, 4, 4, 4, 4, 4, 4, // 0001_1000 - 0001_1111
                        5, 5, 5, 5, 5, 5, 5, 5, // 0010_0000 - 0010_0111
                        5, 5, 5, 5, 5, 5, 5, 5, // 0010_1000 - 0010_1111
                        5, 5, 5, 5, 5, 5, 5, 5, // 0011_0000 - 0011_0111
                        5, 5, 5, 5, 5, 5, 5, 5, // 0011_1000 - 0011_1111
            
                        6, 6, 6, 6, 6, 6, 6, 6, // 0100_0000 - 0100_0111
                        6, 6, 6, 6, 6, 6, 6, 6, // 0100_1000 - 0100_1111
                        6, 6, 6, 6, 6, 6, 6, 6, // 0101_0000 - 0101_0111
                        6, 6, 6, 6, 6, 6, 6, 6, // 0101_1000 - 0101_1111
                        6, 6, 6, 6, 6, 6, 6, 6, // 0110_0000 - 0110_0111
                        6, 6, 6, 6, 6, 6, 6, 6, // 0110_1000 - 0110_1111
                        6, 6, 6, 6, 6, 6, 6, 6, // 0111_0000 - 0111_0111
                        6, 6, 6, 6, 6, 6, 6, 6, // 0111_1000 - 0111_1111
            
                        7, 7, 7, 7, 7, 7, 7, 7, // 1000_0000 - 1000_0111
                        7, 7, 7, 7, 7, 7, 7, 7, // 1000_1000 - 1000_1111
                        7, 7, 7, 7, 7, 7, 7, 7, // 1001_0000 - 1001_0111
                        7, 7, 7, 7, 7, 7, 7, 7, // 1001_1000 - 1001_1111
                        7, 7, 7, 7, 7, 7, 7, 7, // 1010_0000 - 1010_0111
                        7, 7, 7, 7, 7, 7, 7, 7, // 1010_1000 - 1010_1111
                        7, 7, 7, 7, 7, 7, 7, 7, // 1011_0000 - 1011_0111
                        7, 7, 7, 7, 7, 7, 7, 7, // 1011_1000 - 1011_1111
            
                        7, 7, 7, 7, 7, 7, 7, 7, // 1100_0000 - 1100_0111
                        7, 7, 7, 7, 7, 7, 7, 7, // 1100_1000 - 1100_1111
                        7, 7, 7, 7, 7, 7, 7, 7, // 1101_0000 - 1101_0111
                        7, 7, 7, 7, 7, 7, 7, 7, // 1101_1000 - 1101_1111
                        7, 7, 7, 7, 7, 7, 7, 7, // 1110_0000 - 1110_0111
                        7, 7, 7, 7, 7, 7, 7, 7, // 1110_1000 - 1110_1111
                        7, 7, 7, 7, 7, 7, 7, 7, // 1111_0000 - 1111_0111
                        7, 7, 7, 7, 7, 7, 7, 7, // 1111_1000 - 1111_1111
                    };
            
                int byte;
                int byte_cnt;
            
                for (byte_cnt = 3; byte_cnt >= 0; byte_cnt--)
                {
                    byte = (x >> (byte_cnt * 8)) & 0xff;
                    if (byte != 0)
                    {
                        return msb_lut[byte] + (byte_cnt * 8);
                    }
                }
            
                return -1;
            }
            

            【讨论】:

            • 执行速度最快,但不是打字速度;)
            • erickson 的答案更快...它使用的内存更少,因此缓存效率更高。
            【解决方案10】:

            这应该可以解决问题。

            int hob (int num)
            {
                if (!num)
                    return 0;
            
                int ret = 1;
            
                while (num >>= 1)
                    ret <<= 1;
            
                return ret;
            }
            

            hob(1234) 返回 1024
            hob(1024) 返回 1024
            hob(1023) 返回 512

            【讨论】:

            • 要使其与零一起工作,只需添加:while (num >>= 1 && num)
            • 实际上你需要一个 if 来检查是否为零。由于 ret 从 1 开始并增长,因此该算法不会使其达到 0,即输入 0 的答案。
            • 我的回答更好。没有循环!
            • 一个好的编译器会展开循环以提高性能。如果它确实在没有循环的情况下执行得更快。
            • 对于 GCC,更好的函数是 __builtin_clz(v) - 它返回前导(二进制)零的数量,因此您可以通过 32-clz(num) (或 64-clzll(num) 用于 64 位)
            【解决方案11】:

            喜欢混淆代码?试试这个:

            1

            【讨论】:

            • 我喜欢这个解决方案,但它有点太模糊了。仍然获得投票作为最紧凑的解决方案。
            • 如果您想在 FPU 上典当工作,当然可以。 :)
            • 数学上正确,但这可能比按位运算慢得多。
            • 如果您先将其转换为 double,则数字的指数部分很容易告诉您顺序。
            • 小心数字表示。这个配方适用于正 32 位整数。但是std::log2() 为负参数返回nanint(nan) 的计算结果为0x80000000,这是最大的负整数。这可以在 g++7.3 中观察到,但恐怕这不是可移植的。下一个问题是舍入误差。当您输入 64 位数字时,在 2^{48}-1 内一切正常。但是对于 2^{49}-1,配方返回 2^{49}。
            【解决方案12】:

            来自黑客的喜悦:

            int hibit(unsigned int n) {
                n |= (n >>  1);
                n |= (n >>  2);
                n |= (n >>  4);
                n |= (n >>  8);
                n |= (n >> 16);
                return n - (n >> 1);
            }
            

            此版本适用于 32 位整数,但逻辑可以扩展为 64 位或更高。

            【讨论】:

            • 在最后一行使用 XOR 不是应该更高效吗? n ^ (n >> 1)
            • @alveko 虽然 XOR 的实现速度比晶体管/栅极级别的减法更快,但它们在大多数现代 CPU 上将采用相同数量的时钟周期
            • 更少的晶体管,更高的效率,更少的热量,更多的循环 :)
            • 根据直觉,这会将 MSB 向右“展开”,因此 0b100010 变为 0b111111,然后向右移动 0b011111 并减去它,得到 0b100000。
            • 谁能告诉我它背后的理论是什么?
            【解决方案13】:

            不断删除想到的低位......

            int highest_order_bit( int x )
            {
                int y = x;
                do { 
                    x = y;
                    y = x & (x-1); //remove low order bit
                }
                while( y != 0 );
                return x;
            }
            

            【讨论】:

              猜你喜欢
              • 2013-06-06
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-04-12
              • 1970-01-01
              • 2014-12-31
              相关资源
              最近更新 更多