C++---向上取整

1. 什么是向上取整(Ceiling)

向上取整 (Ceiling)是数学中的一个基本概念,表示取大于或等于某个数的最小整数

数学符号是:⌈x⌉\lceil x \rceil⌈x⌉

例子:

  • ⌈2.3⌉=3\lceil 2.3 \rceil = 3⌈2.3⌉=3
  • ⌈2.0⌉=2\lceil 2.0 \rceil = 2⌈2.0⌉=2
  • ⌈−2.3⌉=−2\lceil -2.3 \rceil = -2⌈−2.3⌉=−2 (注意不是 -3,因为 -2 比 -2.3 大)
  • ⌈−2.0⌉=−2\lceil -2.0 \rceil = -2⌈−2.0⌉=−2

对比:

  • 向下取整(floor):取小于或等于 x 的最大整数
  • 四舍五入(round):根据小数部分决定向上或向下取整

2. C++ 中的除法默认是"向零截断"

在 C++ 中,两个整数相除 / 会执行向零截断(truncate towards zero):

  • 正数:等价于向下取整
  • 负数:等价于向上取整
cpp 复制代码
cout << 7 / 3 << endl;   // 2(7/3=2.333,向零截断)
cout << -7 / 3 << endl;  // -2(不是 -3)

这意味着:

  • 直接用 / 不能直接得到数学上的向上取整结果
  • 需要额外处理才能实现真正的向上取整

3. 实现向上取整的三种主流方法

3.1 使用 <cmath> 中的 std::ceil

C++ 标准库提供了 ceil 函数,定义在 <cmath> 中:

cpp 复制代码
#include <iostream>
#include <cmath>
using namespace std;

int main() {
    double x = 7.0 / 3.0;
    cout << ceil(x) << endl; // 3

    double y = -7.0 / 3.0;
    cout << ceil(y) << endl; // -2
}

特点

  • 支持浮点数,能正确处理负数
  • 缺点:需要先转换成浮点类型,否则整数除法已经截断
  • 有精度误差风险:ceil(8.999999999) 可能得到 8 而不是 9

3.2 整数公式法(适用于正数)

公式:⌈ab⌉=a+b−1b(a,b>0)\lceil \frac{a}{b} \rceil = \frac{a + b - 1}{b} \quad (a, b > 0)⌈ba⌉=ba+b−1(a,b>0)

原理

  • 如果 a 能被 b 整除,那么 a + b - 1 除以 b 仍是 a / b
  • 如果不能整除,a + b - 1 会把分子推到下一个能整除的区间

例子:

cpp 复制代码
int a = 7, b = 3;
cout << (a + b - 1) / b << endl; // (7+3-1)/3 = 9/3 = 3 ✅

int a2 = 6, b2 = 3;
cout << (a2 + b2 - 1) / b2 << endl; // (6+3-1)/3 = 8/3 = 2 ✅

适用范围

  • a、b 均为正数
  • 对于负数,需要额外调整

3.3 位运算优化法(b 是 2 的幂)

如果 b 是 2 的幂,可以用位运算代替除法,效率更高:
⌈a2k⌉=(a+(1<<k)−1)>>k\lceil \frac{a}{2^k} \rceil = (a + (1 << k) - 1) >> k⌈2ka⌉=(a+(1<<k)−1)>>k

例子:

cpp 复制代码
int a = 17, k = 3; // b = 8
int ceil_div = (a + (1 << k) - 1) >> k;
// (17 + 8 - 1) >> 3 = 24 >> 3 = 3 ✅

原理

  • (1 << k) - 1 生成 k 个 1 的掩码
  • 加法后右移 k 位相当于整除 2^k

4. 边界情况处理

4.1 a 或 b 为 0

  • 如果 b = 0:无意义(除零错误)
  • 如果 a = 0:向上取整结果为 0

4.2 负数处理

通用安全公式(支持负数):

cpp 复制代码
int ceil_div(int a, int b) {
    if (b == 0) throw runtime_error("Division by zero");
    if ((a > 0 && b > 0) || (a < 0 && b < 0)) {
        // 同号:用 (a + b - 1) / b
        return (a + b - 1) / b;
    } else {
        // 异号:直接除(向零截断等价于向上取整)
        return a / b;
    }
}

5. 性能对比

方法 优点 缺点 适用场景
std::ceil 简单,支持负数 浮点精度风险 不要求极致性能
(a + b - 1) / b 整数运算,无精度问题 仅适用于正数 算法竞赛、正数场景
位运算 速度快 仅适用于 b 是 2 的幂 内存对齐、性能敏感

6. 实战应用场景

6.1 分页计算

cpp 复制代码
int total_items = 100;
int page_size = 10;
int pages = (total_items + page_size - 1) / page_size; // 10 页

6.2 数组分块

cpp 复制代码
int n = 17;
int block_size = 5;
int blocks = (n + block_size - 1) / block_size; // 4 块

6.3 二分查找中的边界计算

LeetCode 2300:

cpp 复制代码
long long t = (success + i - 1) / i - 1;

这里 (success + i - 1) / i 就是对 success / i 向上取整。


7. 完整代码示例

cpp 复制代码
#include <iostream>
#include <cmath>
#include <stdexcept>
using namespace std;

// 通用向上取整函数(支持负数)
int ceil_div(int a, int b) {
    if (b == 0) throw runtime_error("Division by zero");
    if ((a > 0 && b > 0) || (a < 0 && b < 0)) {
        return (a + b - 1) / b;
    } else {
        return a / b;
    }
}

// 位运算优化版(b 是 2 的幂)
int ceil_pow2(int a, int k) {
    return (a + (1 << k) - 1) >> k;
}

int main() {
    // 测试正数
    cout << ceil_div(7, 3) << endl;    // 3
    cout << ceil_div(6, 3) << endl;    // 2
    
    // 测试负数
    cout << ceil_div(-7, 3) << endl;   // -2
    cout << ceil_div(7, -3) << endl;   // -2
    cout << ceil_div(-7, -3) << endl;  // 3
    
    // 测试位运算版本
    cout << ceil_pow2(17, 3) << endl;  // 3 (17/8=2.125)
    
    // 测试标准库函数
    cout << ceil(7.0 / 3.0) << endl;   // 3
    cout << ceil(-7.0 / 3.0) << endl;  // -2
    
    return 0;
}

8. 总结与建议

  1. 正数场景

    • (a + b - 1) / b 最安全高效
    • 如果 b 是 2 的幂,用位运算 (a + (1 << k) - 1) >> k 更快
  2. 负数场景

    • 用通用公式 ceil_div(a, b)
    • 或直接用 std::ceil((double)a / b)
  3. 性能敏感场景

    • 优先考虑位运算(如果适用)
    • 否则用整数公式法
  4. 精度敏感场景

    • 避免直接用浮点数计算,优先整数公式

生活本来就很精彩。只不过有人没发现自己是作者,没发现他们可以按自己的想法创作。 ---约翰·史特列奇

相关推荐
QX_hao3 小时前
【Go】--扩容机制
开发语言·golang
weixin_307779133 小时前
Clickhouse导出库的表、视图、用户和角色定义的SQL语句
开发语言·数据库·算法·clickhouse·自动化
流星白龙4 小时前
【Qt】7.信号和槽_connect函数用法(1)
开发语言·数据库·qt
小龙报4 小时前
《算法通关指南---C++编程篇(1)》
开发语言·c++·程序人生·算法·学习方法·visual studio
Cx330❀4 小时前
《C++ 手搓list容器底层》:从结构原理深度解析到功能实现(附源码版)
开发语言·数据结构·c++·经验分享·算法·list
仰泳的熊猫4 小时前
LeetCode:98. 验证二叉搜索树
数据结构·c++·算法·leetcode
暴力求解4 小时前
C++ --- 模版初阶
c++
CC.GG4 小时前
【C++】STL容器--list的使用
开发语言·c++·list
洲覆5 小时前
基于 clangd 搭建 Redis 6.2 源码阅读与调试环境
开发语言·数据库·redis·缓存