在0与1的计算机世界中,最复杂的运算就是除法了。复杂到什么程度呢?就是不到万不得已的情况下,连编译器自己都不愿意产生除法指令。
备注:以下分析,主要针对的情况为 除数不是2的指数。如果除法是2的指数,尽可以简单地使用移位来运算。比如, 20 / 4 = 20 >> 2 = 5(2 ^ '2' = 4)。
1.例程代码
简单的C程序:
// div5.c - small example for test
#include
int div5(int num)
{
int result;
result = num / 5;
return result;
}
int main()
{
printf("50 / 5 is %d\n", div5(50));
printf("-50 / 5 is %d\n", div5(-50));
return 0;
}
.file "div5.c"
.text
.globl div5
.type div5, @function
div5:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl 8(%ebp), %ecx # ECX = num
movl $1717986919, %edx # EDX = 1717986919 = ((1 << 33) + 3) / 5
movl %ecx, %eax # EAX = num
imull %edx # EDX:EAX = num * 1717986919
sarl %edx # EDX:EAX >> 33 equql to EDX >> 1
movl %ecx, %eax
sarl $31, %eax # retrieve sign bit of num
movl %edx, %ecx
subl %eax, %ecx # adjust negtive result
movl %ecx, %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
leave
ret
.size div5, .-div5
.section .rodata
.LC0:
.string "50 / 5 is %d\n"
.LC1:
.string "-50 / 5 is %d\n"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
movl $50, (%esp)
call div5
movl $.LC0, %edx
movl %eax, 4(%esp)
movl %edx, (%esp)
call printf
movl $-50, (%esp)
call div5
movl $.LC1, %edx
movl %eax, 4(%esp)
movl %edx, (%esp)
call printf
movl $0, %eax
leave
ret
.size main, .-main
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3"
.section .note.GNU-stack,"",@progbits
2.代码分析
通过代码分析,这里将除法转换为乘法的方法就是
x = y / divider = (y * M) >> 33; 其中M = ((1 << 33) + 3) / divider,当divider=5, M=1717986919=0x66666667。
推导证明过程,我没有深入研究,在此也不作赘述。
汇编代码部分,我就乘法指令“IMUL source”,作简单说明。
source为乘数,在使用IMUL指令之前,需将被乘数置于EAX寄存器中,如果结果为64位值(如两个4字节数之间的乘法),最终的结果放置在EDX:EAX寄存器对中,高32位值在EDX中,低32位值在EAX中。将(y * divider)的结果右移33位,其实就是将EDX右移1位。