为什么取模在除数等于2^n的时候可以用按位与替代?

大家在学习的过程中总是经常看到:x % 2^n = x & (2^n - 1);

比如java的ThreadLocal源码中:firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

也就是:一个数取模(求余数),如果除数是2的n次方,那么等于这个 数 按位与 2的n次方-1。

还是不好理解,举一个具体的例子。

十进制

23 % 8 = 2....7

二进制形式

10111 % 1000

等价于:

10111

& 00111 (2^3-1)

= 00111 (7)

经过上面的案例,可以看到,23转换二进制是10111, 8的二进制是1000,转换成2^n-1就是0111。

最终异或的结果就是7。和我们取模计算的结果一样。

重点:为什么会这样?为什么取模会在除数是2的n次方的时候等价于按位与操作。

基本概念
什么是按位与

按位与是一种运算操作,可以理解成加减乘除。按位与的意思是,两个二进制数,只有都为1的时候结果才是1。按位:按照位置一对一对比,与:与操作,两者都为true则是true。

案例: 100001 & 000111 = 000001 。两个二进制数的最后一位都是1,所以按位与的结果是1。

2^n-1 和2^n 之间是什么关系?

2^n: 2^0 2^1 2^2 2^3 2^4

二进制表示: 0001 0010 0100 1000 10000

2^n-1: 0000 0001 0011 0111 011111

可以发现2的n次方实际就是1后面全是0,而2^n-1则是

对比十进制来学习:

118 % 10 = 18 ----118 % 10^1= 8

1231 % 100 = 31 ------1231 % 10^2 = 31

可以发现,求余数就是只保留x^n中的低位,n就是保留的位数,118%10中,n=1,保留的就是1位,也就是8。1231%100中n=2,所以保留2位就是余数,也就是31.

回到二进制中,2^n就相当于十进制中的10,100,1000。而二进制取模就是保留2^n中的n位,就是余数。

那么继续回到23 % 8的案例

23 % 8 = 2....7

二进制形式

10111 % 2^3 保留3位 就是111 =7,就是余数。

所以这就是为什么必须除数需要时2^n的时候才能用按位与替代 %。

那么为什么需要使用2^n-1呢?

上面我们知道了2^n-1就是2^n的1变成0,1后面的0全部变成1。而这个时候,1的个数刚好是n的值

比如 1000 ----> 0111 2^3 。1的个数也是3个。这就有一个特点,通过2^n-1之后的值,前面的全是0,后面的1的个数刚好是n的值。而按位或操作,必须有两个都是1才是1。那么就能保证,按位与得到的结果刚好是被除数的后n位。

举个例子:

23 % 8 = 7

10111

& 00111

------------------

00111 (7)

个人表达能力有限,感觉并没有讲清楚,但是能够弄明白十进制中的例子,代入二进制中就很容易明白了。按位与2^n-1只是为了得到后n位的操作。

相关推荐
@淡 定5 分钟前
Spring中@Autowired注解的实现原理
java·后端·spring
时空无限14 分钟前
Java Buildpack Reference
java·开发语言
爱笑的眼睛111 小时前
超越剪枝与量化:下一代AI模型压缩工具的技术演进与实践
java·人工智能·python·ai
阿里云云原生1 小时前
Android App 崩溃排查指南:阿里云 RUM 如何让你快速从告警到定位根因?
android·java
历程里程碑1 小时前
C++ 9 stack_queue:数据结构的核心奥秘
java·开发语言·数据结构·c++·windows·笔记·算法
醇氧1 小时前
【Windows】从守护到终结:解析一个 Java 服务的优雅停止脚本
java·开发语言·windows
努力发光的程序员1 小时前
互联网大厂Java求职面试实录
java·jvm·线程池·多线程·hashmap·juc·arraylist
小鹿学程序2 小时前
FileZilla连接到虚拟机
java·服务器·开发语言
Haooog2 小时前
Docker面试题(不定时更新)
java·docker·面试
feathered-feathered2 小时前
Redis基础知识+RDB+AOF(面试)
java·数据库·redis·分布式·后端·中间件·面试