火龙信奥
  • 分享
  • 课程
  • 题库
  • 打卡
    • 代码对战
    • 快速对战
  • 题单
  • 团队
  • 荣誉墙
  • 商城
  • 登录 / 注册

不大于 $x$ 的最大 2 的幂次方

作者: 作者的头像   huolong , 时间:2026-08-21 17:09:07 , 所有人可见, 阅读  54

在位运算和数论中,快速寻找不大于 $x$ 的最大 2 的幂次方(数学上表示为 $2^{\lfloor \log_2 x \rfloor}$)是一个经典且非常实用的问题。在底层算法(如哈希表扩容、树状数组、二分搜索优化)中经常会用到。

下面为您介绍几种在 C++ 中快速实现该功能的方法,从现代 C++ 标准库到高效的纯位运算。


方法 1:C++20 标准库 std::bit_floor(最现代、安全)

如果您可以使用 C++20,标准库 <bit> 头文件中已经直接提供了该功能函数 std::bit_floor:

#include <bit>
#include <iostream>

int main() {
    unsigned int x = 5;
    // 返回不大于 x 的最大 2 的幂次方
    std::cout << std::bit_floor(x) << std::endl; // 输出 4
    return 0;
}
  • 优点:由编译器高度优化,安全且可读性极佳。

方法 2:利用编译器内置指令(C++11 常用,最快 $O(1)$)

在 C++11/14/17 中,我们可以使用 GCC/Clang 提供的编译器内置指令(Intrinsics)来直接获取 CPU 级别的硬件加速。

其核心思想是:计算 $x$ 的二进制表示中前导零(Leading Zeros)的个数。 例如,32 位整数中,若最高位的 1 在第 $k$ 位(从右往左数,0-indexed),那么前导零的个数就是 $31 - k$。因此,最高位的 1 代表的值就是 $1 \ll k$。

#include <iostream>

uint32_t highestPowerOfTwo(uint32_t x) {
    if (x == 0) return 0;
    // __builtin_clz(x) 返回 x 的前导零个数
    int leading_zeros = __builtin_clz(x); 
    return 1U << (31 - leading_zeros);
}

int main() {
    std::cout << highestPowerOfTwo(5) << std::endl;  // 输出 4 (2^2)
    std::cout << highestPowerOfTwo(8) << std::endl;  // 输出 8 (2^3)
    std::cout << highestPowerOfTwo(15) << std::endl; // 输出 8 (2^3)
    return 0;
}
  • 优点:通常会直接编译成单条汇编指令(如 x86 上的 BSR 或 LZCNT),速度是所有方法中最快的。
  • 注意:传入 0 会导致未定义行为,因此需要先特判 x == 0。对于 64 位无符号整数,应使用 __builtin_clzll。

方法 3:经典的二进制位扩散算法(跨平台、硬件无关 $O(1)$)

如果您需要编写不依赖任何编译器特性(如 MSVC、GCC 均通用)的跨平台代码,可以使用经典的书籍《黑客秘笈》(Hacker's Delight)中的位扩散(Bit Smearing)算法。

算法逻辑:

通过逐步右移并执行“按位或(|)”操作,将最高位的 1 往右的所有二进制位全部“污染”成 1。 例如,设 32 位无符号整数 $x$: 1. x |= x >> 1;(使得最高位及紧邻其右的一位都变为 1) 2. x |= x >> 2;(使得高 4 位都变为 1) 3. x |= x >> 4; 4. x |= x >> 8; 5. x |= x >> 16;(至此,最高位往右的所有位已全部变为 1)

此时的 $x$ 在二进制下会变成类似于 00...01111 的形式。 接着,我们只需通过 x - (x >> 1) 或者 x ^ (x >> 1),就能将低位的 1 全部抹去,仅保留最高位的 1。

C++ 代码实现:

#include <iostream>

uint32_t highestPowerOfTwoBitwise(uint32_t x) {
    if (x == 0) return 0;

    // 扩散最高位的 1 到所有低位
    x |= x >> 1;
    x |= x >> 2;
    x |= x >> 4;
    x |= x >> 8;
    x |= x >> 16;

    // 抹去低位的 1,只留最高位
    return x - (x >> 1); // 也可以用 x ^ (x >> 1)
}

int main() {
    std::cout << highestPowerOfTwoBitwise(5) << std::endl;  // 5 (0101) -> 7 (0111) -> 4 (0100)
    std::cout << highestPowerOfTwoBitwise(1) << std::endl;  // 1 (0001) -> 1 (0001) -> 1 (0001)
    return 0;
}
  • 数学演示(以 $x = 5$ 为例):
  • $5$ 的二进制是 0101
  • 执行扩散后,变为 0111(即 7)
  • x >> 1 得到 0011(即 3)
  • x - (x >> 1) 计算为 $7 - 3 = 4$(即 0100),正好是不大于 5 的最大 2 的幂。

四、 相关拓展问题

在解决类似数论/位运算问题时,还有两个极为相似且频繁被问及的问题:

1. 寻找 严格小于 $x$ 的最大 2 的幂

如果题目要求的是“严格小于”,那么当 $x = 8$ 时,我们应该返回 $4$。 * 解决思路:只需对输入进行预处理,将问题转化为求“不大于 $x-1$ 的最大 2 的幂”。 * 实现:在上述任何方法的开头,将 x 替换为 x - 1 即可(注意特判 x <= 1 的边界情况)。

2. 寻找 能整除 $x$ 的最大 2 的幂(Lowbit)

在树状数组(Binary Indexed Tree)中,我们经常需要寻找能整除 $x$ 的最大 2 的幂(即 $x$ 的二进制表示中,最右侧、最低位的 1 所代表的值)。 * 解决思路:利用计算机的补码特性,这可以直接通过一条极其简单的位运算实现: $$lowbit(x) = x \ \& \ (-x)$$ * 原理:$-x$ 在补码表示下等于 ~x + 1。在最右侧的 1 之前的所有位会全部取反,而最右侧的 1 及其右边的 0 保持不变,两者按位与后便只剩最右侧的 1。 * 示例: * 若 $x = 12$(二进制 1100),则 $12\ \&\ (-12)$ 返回 $4$(二进制 0100)。因为 4 是能整除 12 的最大 2 的幂。

—— 本文来自火龙信奥(义乌睿码科技):义乌青少年信息学奥赛与编程教育平台,专注 CSP-J/S、NOIP、GESP 竞赛培训,线上线下融合教学,助力编程升学。网址:hlcoding.com

关于火龙

  • 关于我们
  • 学员获奖
  • 预约试听
  • ACM课程
  • CSP课程
  • 学习指南

帮助中心

  • 用户协议
  • 打字练习
  • 在线画图
  • DevC++下载
  • CSP报名
  • GESP官网

推荐课程

  • C++零基础入门(可试看)
  • C++进阶提升
  • GESP考级辅导
  • GESP打卡
  • CSP-J/S打卡

公众号

火龙信奥公众号二维码

地址:义乌市北门街188号新天地商厦二楼2F 邮箱:wdlok305@126.com

© 2017-2026 义乌市睿码科技有限公司版权所有 浙ICP备2021013995号

火龙信奥
请输入登录信息


请完成安全验证
验证码底图 滑块
向右拖动滑块完成验证
请输入用户名 / 绑定的手机号码



请输入注册信息(手机号验证码注册)





验证码5分钟有效,60秒内不可重复获取,每日最多3次

微信登录

微信登录二维码

正在生成二维码...

账号已过期,请续期。
去续期

绑定手机号

📱

为了更好地保护您的账号安全,享受完整的平台服务

请您尽快绑定手机号码