跳转至

整数向上取整惯用法:(N+7)/8

(N+7)/8 在整数域模拟向上取整,位打包、分页等容量计算场景的标准技巧。

背景

Go 的整数除法只向 取整(截断):9/8 == 1。但很多场景需要向 取整——最典型的是位打包(bit packing):N 个比特位需要分配多少字节存储。1 位也要占 1 字节,9 位就得占 2 字节,位数不满 8 的倍数时必须分配足够的容量。

数学上的需求是:

所需字节数 = ceil(N / 8)          // N 位需要多少字节(向上取整)

Go 没有内置整数向上取整,标准做法是利用恒等式,用纯整数运算模拟:

ceil(N/8) = (N + 7) / 8           // 整数除法

核心内容

为什么是 +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),通用写法:

ceil(a / b) = (a + b - 1) / b    // b > 0

常见应用场景:

  • 位打包(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 的整数除法同样是截断,同一惯用法通用

延伸阅读


维护人:yiiewang · 最后更新:2026-09-04

评论