在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次冥的取余怎么来实现呢?
原文地址:http://blog.sina.com.cn/s/blog_645f168c0100ib7e.html