求解一个数的二进制最高位

求解一个数的二进制最高位是一个常见问题。具体来说,5 的二进制是 101,其最高位在第 2 位(假定最低位是0)。30 的二进制是 11110,最高位是第 4 位。我们怎么求解这个位数呢?

方案一:逐位遍历

从低位向高位逐渐遍历即可,无需解释。当然也有很多种写法。这里提供一种。

int highestBit(int num) {
    if (num == 0) return 0;

    int pos = 0;
    while (num > 1) {
        num >>= 1;
        pos++;
    }
    return pos;
}

方案二:部分打表

还有一个思路是空间换时间,对小范围的数据打表。假如我们首先计算出 lookup[256] 表示 0-255各自的最高位位置。由于 int 可以划分成8位为一块进行处理,我们先检查高16位是否有值,若有则只考虑高 16 位,否则只考虑低 16 位。同理再划分一次,我们则可以将范围缩小到 8 位,调用计算好的结果即可。

const int lookup[256] = {
    0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
    5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
};

int highestBit(int num) {
    if (num == 0) return 0;
    int pos = 0;
    if (num & 0xFFFF0000) { pos += 16; num >>= 16; }
    if (num & 0xFF00) { pos += 8; num >>= 8; }
    return pos + lookup[num];
}

方案三:内置函数

在 C+ 中,GCC 和 Clang 编译器提供了一个内置函数 __builtin_clz,用于计算一个整数的前导零的数量。结合这个函数,可以快速找到一个整数的最高位1的位置。

#include <climits> // for CHAR_BIT

int highestBit(int num) {
    if (num == 0) return 0;
    return sizeof(num) * CHAR_BIT - 1 - __builtin_clz(num);
}

__builtin_clz(num):返回整数 num 的前导零的数量。sizeof(num) * CHAR_BIT:表示整数 num 的总位数。例如,对于32位整数,这个值是32。相减再减一就是正确答案。

使用 __builtin_clz 的效率通常非常高,这是因为它通常会被编译器翻译成一条高效的汇编指令。例如,在x86架构上,它通常被翻译成一条BSR(Bit Scan Reverse)或LZCNT(Leading Zero Count)指令,这些指令在硬件级别上非常高效。

大多数现代CPU都支持相关指令,在其他编译器(如MSVC)上可能需要不同的方法。可自行查阅。

注意处理负数

由于补码的性质,负数不存在最高位1。如果一定要定义一个,那么就是第31位(即表示符号的最高位),上面的函数均没有考虑负数情况,请多加注意。

posted @ 2024-07-12 16:01  Ofnoname  阅读(16)  评论(0编辑  收藏  举报