n > 0 && (n & (n - 1)) == 0 能正确判断2的幂,因2的幂二进制仅一个1,n-1使该位变0、右侧全1,按位与得0;但必须先排除非正数,否则0和负数导致误判或未定义行为。

为什么 n & (n - 1) 能判断2的幂
因为2的幂在二进制中只有一位是1,其余全是0(比如 1、10、100)。而 n - 1 会把那个唯一的1变成0,并把右边所有0变成1。两者按位与结果必为0。
但要注意边界:0不是2的幂,负数也不可能是2的幂(C++中整数默认有符号),所以必须先排除非正数。
常见错误现象:n == 0 时 n & (n - 1) 算出来也是0,误判为true;n 时行为未定义或逻辑错乱。
- 正确写法:
n > 0 && (n & (n - 1)) == 0 -
n必须是无符号类型或确保为正,否则对负数做位运算可能依赖实现 - 该表达式对
int、long long、unsigned int都适用,但要注意n类型不能是bool或窄类型(如char)——隐式提升后仍有效,但语义不清晰
用 std::popcount(C++20)更直观但稍慢
C++20 引入了 std::popcount,直接数二进制中1的个数。2的幂恰好只有一个1,所以可写成 n > 0 && std::popcount(static_cast<unsigned>(n)) == 1</unsigned>。
它比 n & (n - 1) 更易读,但底层通常仍靠硬件指令(如 x86 的 popcnt),性能差距微小;不过在不支持该指令的老CPU上可能退化为查表或循环,而 & 方案始终是单条指令。
- 必须包含
<bit></bit>头文件 - 参数必须是无符号整型,传
int需显式转成unsigned或unsigned long long,否则编译失败 - 对
0返回0,对负数若误传会触发未定义行为(因签名整数转无符号是模运算,-1 变成全1,popcount结果很大)
处理 0 和最小负数这些陷阱值
最容易被忽略的是 INT_MIN(如 -2147483648)。对有符号整数执行 n - 1 可能溢出,导致未定义行为。即使没崩溃,n & (n - 1) 在负数上也毫无数学意义。
所以安全前提永远是先过滤:只有正整数才值得位运算判断。
- 不要写
!(n & (n - 1))—— 这会把0和负数都“判对” - 如果输入可能为0,必须显式写
n != 0或n > 0;前者更快(避免符号比较),但后者语义更准(2的幂必为正) - 若函数接口允许
unsigned输入(如uint32_t),则只需n && !(n & (n - 1)),因为无符号0减1会回绕成全1,但n == 0时短路已跳过后续计算
编译器是否真会优化成一条指令
主流编译器(GCC/Clang/MSVC)在O2及以上优化级别下,对 n > 0 && (n & (n - 1)) == 0 会识别为“is power of two”模式,并生成极简汇编:通常是 test + jz + lea + and + cmp,现代CPU上基本是2~3周期。
但如果你把逻辑拆成多步变量(比如先算 n - 1 存变量,再 &,再比较),可能阻碍优化。保持一行表达式更可靠。
- 避免写成:
auto m = n - 1; return n > 0 && (n & m) == 0;—— 编译器不一定合并 - 模板函数中若
n是consteval或字面量,编译期就能算出结果 - 对
constexpr场景,两种方案都支持编译期求值,但&方案兼容C++11,popcount要C++20
真正卡住人的从来不是公式本身,而是忘了 n 时整个位运算逻辑就失效了。哪怕只漏一个 <code>n > 0 判断,测试用例跑过去,线上遇到0或负数就悄悄出错。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











