在底层实现和性能优化(如 HashMap 的实现原理之索引计算)中,有这么一个等式经常被使用:
a % b == a & (b - 1)
可以认为这是一个高效取模运算的技巧,下面是该公式的使用条件限制和标准证明
描述
对于任意非负整数
a
和任意正整数b
,如果b
是 2 的整数次幂,那么a
对b
取模的结果等价于a
与(b - 1)
进行 按位与(&) 运算的结果
形式化定义:
- 令
a
为一个整数,且a ≥ 0
- 令
b
为一个整数,且b = 2^n
,其中n
为一个非负整数 (n ≥ 0
)- 则以下等式恒成立:
a % b == a & (b - 1)
证明
为了证明这个定理,我们需要从二进制表示的角度来理解取模运算和按位与运算
1. 分析 b
和 b - 1
的二进制形式
- 因为
b
是 2 的 n 次幂 (b = 2^n
,其中n ≥ 1
以使问题有意义),其二进制表示为一个1
后面跟着n
个0
- 例如,如果
b = 16
(2^4
),其二进制为10000
- 例如,如果
- 那么
b - 1
的二进制表示则是由n
个1
组成- 例如,如果
b = 16
,那么b - 1 = 15
,其二进制为01111
- 例如,如果
所以,b - 1
是一个低位掩码,它的低 n
位全部为 1
,而所有更高位都为 0
2. 分析取模运算 a % b
- 取模运算
a % b
的结果是a
除以b
的余数r
根据除法原理,任何非负整数a
都可以表示为:
a = q * b + r
其中q
是商,r
是余数,且0 ≤ r < b
- 将
b = 2^n
代入,我们得到:
a = q * 2^n + r
- 在二进制中,乘以
2^n
相当于左移n
位这意味着q * 2^n
这个数的二进制表示中,其低n
位必然全部为0
- 因为
0 ≤ r < b
,即0 ≤ r < 2^n
,所以余数r
的二进制表示最多只需要n
位 - 当我们将
q * 2^n
和r
相加时,由于q * 2^n
的低n
位都是0
,所以相加的结果a
的低n
位就完全由r
决定换句话说,a
的低n
位组成的数就是余数r
3. 分析按位与运算 a & (b - 1)
- 我们已经知道
b - 1
是一个低n
位全为1
,高位全为0
的掩码 - 当
a
与(b - 1)
进行按位与运算时:a
的高位部分与(b - 1)
的高位0
相与,结果全部为0
a
的低n
位部分与(b - 1)
的低n
位1
相与,结果保持a
的低n
位不变
- 因此,
a & (b - 1)
的运算结果就是提取出a
的低n
位所代表的数值
4. 结论
- 从第 2 步我们得出:
a % b
的结果是a
的低n
位所代表的数值 - 从第 3 步我们得出:
a & (b - 1)
的结果也是a
的低n
位所代表的数值
因为两者都等于 a
的低 n
位所代表的数值,所以 a % b == a & (b - 1)
证明完毕
举例验证:
- 令
a = 21
,b = 16
a
的二进制:10101
b
的二进制:10000
(b = 2^4
, n=4)b - 1
的二进制:01111
取模运算:
21 % 16 = 5
按位与运算:
10101 (a = 21)
& 01111 (b - 1 = 15)
---------
00101 (结果为 5)
两者结果相等,定理成立