为什么取模在除数等于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位的操作。

相关推荐
啃火龙果的兔子1 小时前
webview焦点与软键盘键盘交互详解
计算机外设·交互
7***37451 小时前
Java设计模式之工厂
java·开发语言·设计模式
程序员小白条2 小时前
你面试时吹过最大的牛是什么?
java·开发语言·数据库·阿里云·面试·职场和发展·毕设
折翅嘀皇虫2 小时前
fastdds.type_propagation 详解
java·服务器·前端
小年糕是糕手2 小时前
【C++】类和对象(二) -- 构造函数、析构函数
java·c语言·开发语言·数据结构·c++·算法·leetcode
豐儀麟阁贵2 小时前
8.2异常的抛出与捕捉
java·开发语言·python
老华带你飞2 小时前
社区养老保障|智慧养老|基于springboot+小程序社区养老保障系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·小程序·毕设·社区养老保障
码龄3年 审核中2 小时前
Linux record 03
java·linux·运维