整数向上取整惯用法:(N+7)/8
用
(N+7)/8在整数域模拟向上取整,位打包、分页等容量计算场景的标准技巧。
背景
Go 的整数除法只向 下 取整(截断):9/8 == 1。但很多场景需要向 上 取整——最典型的是位打包(bit packing):N 个比特位需要分配多少字节存储。1 位也要占 1 字节,9 位就得占 2 字节,位数不满 8 的倍数时必须分配足够的容量。
数学上的需求是:
Go 没有内置整数向上取整,标准做法是利用恒等式,用纯整数运算模拟:
核心内容
为什么是 +7
+7 的作用是:当 N 不是 8 的倍数时,把商"顶"过整数边界。
| N(位数) | 直接 N/8 ❌ | (N+7)/8 ✅ | 说明 |
|---|---|---|---|
| 1 | 0 字节 | 1 字节 | 0 字节放不下 1 位,越界 |
| 8 | 1 字节 | 1 字节 | 正好整除,+7 不影响(余数 7 被除法舍掉) |
| 9 | 1 字节 | 2 字节 | 1 字节只有 8 位,放不下第 9 位 |
| 12 | 1 字节 | 2 字节 | 12 位需要 16 位空间 |
| 16 | 2 字节 | 2 字节 | 正好整除 |
关键观察:N 是 8 的倍数时 +7 完全无副作用(余数 7 被整数除法吸收),非倍数时恰好多分配 1 字节——所以它对所有 N 都正确。
通用形式
这是整数编程的常见惯用法(idiom),通用写法:
常见应用场景:
- 位打包:
(len(bits)+7)/8计算 N 位所需的字节数 - 分页计算:
(total + pageSize - 1) / pageSize计算总页数 - 分组分配:N 个东西按每份 K 个分组,最后一份可能不满
凡是"N 个东西按每份 K 个分组、最后一份可能不满"的场景都用它。
示例:位打包 encodeBits
(len(bits)+7)/8 的典型应用——把 []bool 压缩成紧凑的字节位图:
func encodeBits(bits []bool) []byte {
out := make([]byte, (len(bits)+7)/8) // 12 位 → 2 字节(16 位容量,末 4 位闲置为 0)
for i, b := range bits {
if b {
out[i/8] |= 1 << (7 - uint(i%8)) // 第 i 位落在第 i/8 个字节的第 i%8 个 bit
}
}
return out
}
配套的解码侧只读前 wantBits 位:
func decodeBits(data []byte, wantBits int) []bool {
bits := make([]bool, wantBits)
for i := range bits {
bits[i] = data[i/8]&(1<<(7-uint(i%8))) != 0
}
return bits
}
位序约定:第 i 位落在第 i/8 个字节的第 i%8 个 bit(MSB 在前的大端位序)。除法与取模在这里配对使用:i/8 定位字节,i%8 定位字节内的 bit。
注意事项
- ⚠️ 只能用于非负整数:Go 的
/对负数是向零截断,(a+b-1)/b对负数 a 不成立 - ⚠️ 加法可能溢出:a 接近类型最大值时
a+b-1会溢出,极端场景改用溢出安全写法(a-1)/b + 1(要求 a > 0) - ✅ 位打包的末尾不足 8 位的部分补 0 闲置(不编码任何槽位),解码侧只读前
wantBits位,多余的 0 自动忽略——"补齐"只发生在编码侧的容量计算,不影响语义 - ✅ 语言无关:C/C++/Java/Rust 的整数除法同样是截断,同一惯用法通用
延伸阅读
- Go 并发编程:锁与同步全景 — 同子域知识
- Go 语言规范:算术运算符
维护人:yiiewang · 最后更新:2026-09-04