位运算与余数

背景:在C风格语言中(比如C,C++,C# (注:排名按出生日期 ^_^)),取余运算符定义为“%”。但在很久很久以前,CPU采用如下方法计算余数(注意,该方法只对2的N次方数系有效):

X & (2^N - 1)

 

看到这个算法,我感到比较好奇,为什么这么做呢?

 

首先从求余数谈起,我们知道,计算机中存储的方式是0和1序列:

1 0001 2^0

2 0010 2^1

3 0011 2^1 + 1

4 0100 2^2

当我们把这些数字序列左移一位的时候... 是的,你答对了,相当与数字扩大为原来的2倍,同理可知右移一位就是缩小为原来的1/2。

 

那么余数在哪里?被移掉的哪些位便是余数,这是无数前人的证明结果~可以自己拿笔和纸画一画就明白了。

 

那么,说了这么多,为什么一个求余的%被替换为:X & (2^N - 1)?

举例:

9 的二进制就是1001

8 的二进制就是1000

显然余数是1,按照公式这么做:

把8的二进制1000换为8-1也就是7,7的二进制表示为:0111

1001&0111 = 0001

答案为1与我们想的结果一致。

是巧合么?我们把9换成13,过程你亲自来一遍,结果在你没算错的情况下必然和我的一样:0101

 

但是注意,这种方法只是适合与求一个数除以二的N次冥才正确。

先说原理再说疑问:

原理:

由于除数是2^N (N为0起始递增的整数),所以隐藏条件就是,除数的2进制真身只能是如下的方式展现:

0001,0010,0100,1000.。。。。。

没看出来?那我们继续:

10000,100000,1000000.。。。。

明白了吧,当我们求余的时候,相当于除以2的N次冥,也就是相当于把数本身右移N位,但是移走的那些余数可一去不复返了。(什么?你说在CF寄存器中?嗯,这是个好注意,也许有另种一实现?!)。有什么办法保护这些即将失去的数据呢?

我们把上面的数字减一就发现:

0000,0001,0011,0111,01111,011111,0111111.。。。。。。

如您所见,当和1做位运算时候,得到的结果是那个数的本身。前面例子1001&0111= 0001相当于1001右移三位得到0001,那么(13)1101右移三位还是0001,这2个0001表示的是商,两个移走的余数分别是“001”和“101”,发现特征了嚒?除以2的N次方就是要保存被除数即将被移走的低N位。

好了,我知道上面我讲的可能不清楚,但是下面才是真正惊险的地方:

2^3=8 然后8表示为1000 有3个零,预示着右移位数,右移位数告诉我们被除数即将被移走几位,我们如果将这几位与1按位运算不就能知道这几位到底是0还是1,也就是能完全保存到移走的数据,它就是我们期望的余数。

 

嗯,我确信看完我的解释你还在晕眩之中,别管他们,该干嘛干嘛,睡完一觉之后我想我的这些话也许会被你的大脑自动解析,你最终也会明白这是怎么一回事。

 

也许还有极个别头脑超好的人并切超有毅力的看到这里,他们会问,你说的疑问是什么?

嗯,我想说,如果早起电脑都这么做的话,那么求一个非2的N次冥的取余怎么来实现呢?

如果你知道,请告诉我^_^

Java 位运算和模运算到底哪个快?修改 之前在看 ```HashMap``` 的源代码和相关博客。 看到了```HashMap```中有关```HashMap```容器大小和```indexFor()```中的方法。 1. ```HashMap```为什么长度规定需要是2的n次方。 2. ```HashMap```中```indexFor()```方法中计算```index```是通过位运算(&)来的。 按照我的理解来看,之所以要是2的n次方,是为了能使```indexFor```中的位运算代替取模运算。 > 有些人2^n-1 正好二进制位全是1,类似这种```1111111```,这样与运算```hash```冲突才能降到到最低,但是直接取模不是一样的能均匀分布么? 这里关键的问题就在于究竟是不是位运算比取模运算快很多了: ``` public static void test2() { int capacity = 1024; int val = 13; int count = 100000; long t1 = System.nanoTime(); for (int i = 0; i < count; i++) { indexFor1(val,capacity); } long t2 = System.nanoTime(); System.out.println("& time:"+(t2-t1)); long t3 = System.nanoTime(); for (int i = 0; i < count; i++) { indexFor2(val,capacity); } long t4 = System.nanoTime(); System.out.println("% time:"+(t4-t3)); } static int indexFor1(int h, int length) { return h & (length-1); } static int indexFor2(int h, int length) { return h % (length); } ``` 这段测试代码在我的电脑上,相差结果并不大。。。 突然就迷惘了。。 楼主之前学过计组,操作系统等学过,也知道取模是肯定比直接位运算慢。但是为什么执行起来相差不多。 > 1.难道是因为现在cpu发展已经有了很大的优化,所以相差不打,但是在```HashMap```刚出现的时候,位运算和取模效率相差很大,所以用了位运算,而现在JDK 1.8 还是位运算是为了兼容以前的程序。 是这样么?只是我的猜想,或者是我理解不对?测试方法写的不对? 望解惑,要是是我理解不多,以后这种设计方法可以借鉴啊。
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页