2的幂取模优化

第一种2的幂取模优化

若被除数是正数,只需取低k位的值即可。

eg. 若k取3,则除数为8,被除数为9,则模数为9的低3位,001(2)

可以这样做的原因是第k+1位的值等于2k,也就是说大于等于k+1位的值都大于\(2^k\),他们的取值不影响余数

余数的取值范围为 \([0,2^k-1]\)

若被除数是负数,取低k位的值,还要将k位前的高位补1。

eg. 若k取3,则除数为8,被除数为-9,则模数为-9的低3位,111(2),将高位补1为\(FFFFFFFF = -1\)

由于余数的取值范围为\([-2^k,-1],当余数为2^k时,其实真正的余数应当为0\)

eg. 若k取3,则除数为8,被除数为-8,则模数为-8的低3位,000(2),将高位补1为\(FFFFFFF8 = -8\)

那么如何将-2^k,变为0呢,编译器用了一个非常巧妙的办法,就是先减1,再在高位补1,再加1。

eg. 若k取3,则除数为8,被除数为-8,则模数为-8的低3位,000(2),先减1,得111将高位补1为\(FFFFFFFF\),再加1,得0

其他的余数取值,由于减1加1没有影响高位,所以加减相互抵消,不会影响最终的取值

看看实际的汇编代码

//当eax为-8时,

and eax,80000007 //eax=80000000

//由于eax<0,

执行dec eax //eax = 7FFFFFFF

or eax,FFFFFFF8 //eax = FFFFFFFF

inc eax //eax = 00000000

第二种2的幂取模优化

若被除数是正数,和上面一样

若被除数是负数且余数的取值范围为\((-2^k,-1]\),取低k位的值,还要将k位前的高位补1即可,关键的地方是当余数为\(-2^k,如何把-2^k变为0\)

与上面不同的是,这里采用的是加7,取低k位,再减7的形式,下面用例子说明为什么这样取余可行

eg. 若k取3,则除数为8,被除数为-9,则模数为-9的低3位,111(2),加7得110,取低三位得00000006,再减7得FFFFFFFF=-1

上面的例子,-9的低三位为-1,-1+7=6,然后再减7=-1

通过这样的方法巧妙的达到了与取低3位,再高位补1的效果

eg. 若k取3,则除数为8,被除数为-8,则模数为-8的低3位,000(2),加7得111,取低三位得00000007,再减7得00000000=0

由于当余数为0时,加7减7没有发生溢出,所以不会将高位补1,0就是0

看看实际的汇编代码

//当eax为-8时,

cdq //eax=FFFFFFF8 edx = FFFFFFFF

shr edx,1D //edx = 00000007

add eax,edx //eax = FFFFFFFF

and eax,7 //eax = 00000007

sub eax,edx //eax = 00000000

//当eax为-9时,

cdq //eax=FFFFFFF7 edx = FFFFFFFF

shr edx,1D //edx = 00000007

add eax,edx //eax = FFFFFFFE

and eax,7 //eax = 00000006

sub eax,edx //eax = FFFFFFFF

第三种2的幂取模优化

若被除数是正数,没什么好说的,和上面一样。

若被除数是负数,和之前两种取巧的方式就不一样了,这里用了数学计算,首先将被除数加上\(2^k-1,再取相对于2^k的最大整数,然后再用被除数减取结果,即可得到余数\)

设a为被除数,b为除数,q为商,r为余数

\[r = a-qb \]

那么关键的地方就是算出qb,

由于在c/c++中,整数除法是向零取整,所以这里的q等于上图的x+1,也就是说\(\frac{a}{b}\)取上整等于q

eg. 若k取3,则除数为8,被除数为-9,加7得FFFFFFFE,取相对于8的最大整数的得FFFFFFF8,用FFFFFFF7 - FFFFFFF8 = FFFFFFFF=-1

看看实际的汇编代码

//当eax为-9时,

sar ecx,1F //ecx = FFFFFFFF

shr ecx,1D //ecx = 00000007

add ecx,eax //eax = FFFFFFFE

and ecx,FFFFFFF8 //ecx = FFFFFFF8

sub eax,edx //eax = FFFFFFFF

posted @ 2022-01-04 00:37  乘舟凉  阅读(253)  评论(0编辑  收藏  举报