二进制除法转乘法原理

来源:互联网 发布:淘宝有什么好看的衣服 编辑:程序博客网 时间:2024/05/21 10:09
为了加快运行速度,编译器会将一些除法运算转化成乘法运算。

其中一个转换x/k的方法是,先将k变成1/k 的小数形式,再将1/k 左移N位
得到一个数b

以后每计算 x/k 时,就先计算 x∗b的乘积,再将乘积右移N位得到结果,这样就把耗时的除法运算变成了一个乘法和一个右移运算。

原理:



下面是一段测试C代码以及编译器生成的汇编代码:计算变量除以17的值

void test (){    int num = 18;    int res = num/17;    printf("res:%d\n",res);}



    ##计算num/17    pushq    %rbp    .cfi_def_cfa_offset 16    .cfi_offset 6, -16    movq    %rsp, %rbp    .cfi_def_cfa_register 6    subq    $16, %rsp    movl    $18, -8(%rbp)    movl    -8(%rbp), %ecx                 # %ecx=18    movl    $2021161081, %edx              #  2021161081 ≈ (1/17)*2^35    movl    %ecx, %eax                     # %eax=%ecx=18    imull    %edx                          # 计算2021161081*18的值,并将乘积的高32位存到%edx里。此时%edx的值相当于乘积右移32位后的值    sarl    $3, %edx                       #将%edx的值算术右移3位,即乘积总共右移了35位    movl    %ecx, %eax    sarl    $31, %eax                      #拓展被除数num的符号位,num为负数则 %eax=-1,正数则为零。此值作为结果的一个修正量。    subl    %eax, %edx                     # %edx =%edx-%eax   ,若被除数为负数,则结果减去-1(加1)。    movl    %edx, %eax                     #最后打印结果。    movl    %eax, -4(%rbp)    movl    -4(%rbp), %eax    movl    %eax, %esi    movl    $.LC0, %edi    movl    $0, %eax    call    printf    nop




我猜测:之所以最后有个修正量,是由于实际的乘数y是一个约数,跟理论上的b值存在误差。

假设实际乘数y跟理论值b之间的误差为k(y > b,k为正数),则有:

 y = b+k x*y = x*(b+k) = xb+xk

当x为正数时,实际的乘积就会比理论的大xk

当x为负数时,实际的乘积就会比理论的小xk

从上面可以看出,右移操作是向下舍入,x为正数时多出的xk会因为向下舍入而得到修正。

当x为负数时,乘积会因为向下舍入和减去的xk而比理论的值小,因此要加上1作为修正。