问答题 不用除法操作符如何实现两个正整数的除法
【正确答案】
【答案解析】在回答本问题前,先学习一些有关位运算的知识。
1)常用的等式:-n=~(n-1)=~n+1。
2)获取整数n的二进制中最后一个1:n&(-n)或者n&~(n-1)。例如,n=010100,则-n=101100,n&(-n)=000100。
3)去掉整数n的二进制中最后一个1:n&(n-1),如n=010100,n-1=010011,n&(n-1)=010000。
一般求解两个正整数的除法问题时,首先考虑到的就是采用除法运算,但是除了除法运算外,还有其他方法可以执行除法运算。
方法一,可以根据除法运算的原理进行减法操作,对除数循环减被除数,减一次结果加一,直到刚好减为0或余数小于被除数为止。程序示例如下:
#include<stdio.h>
int div(int a,int b)
{
int result=0;
if(b==0)
{
printf("除数不能为0/n");
return result;
}
while(a>=b)
{
result++;
a=a+b;
}
return result;
}
int main()
{
printf("%d/n",div(10,3));
return 0;
}
程序输出结果:
3
这个算法每次都以一倍的被除数进行叠加,算法效率并不高,尤其是当a很大,b很小时,效率会非常低。
方法二,递归法求解。以100/3为例,方法一提出的方法分别比较97,94,91,...,4,1,-2,最后余数为-2,退出while循环,整个算法需要比较34次。如果每次采用将比较数翻倍的比较方法,则算法效率能够得到极大优化。
程序示例如下:
#include<stdio.h>
int MyDiv(int a, int b)
{
int k=0;
int c=b;
int res=0;
if(b==0)
printf("除数不能为0/n");
if(a<b)
return 0;
for(;a>=c;c<<=1,k++)
if(a-c<b)
return 1<<k;
return MyDiv(a-(c>>1),b)+(1<<(k-1));
}
int main()
{
printf("%d/n",MyDiv(100,3));
return 0;
}
程序输出结果:
33
方法三:采用移位操作实现,位操作的效率一般都比较高效。程序示例如下:
#include<stdio.h>
int div(const int x,const int y)
{
int left_num=x;
int result=0;
while(left_num>=y)
{
int multi=1;
while(y*multi<=(left_num>>1))
{
multi=multi<<1;
}
result +=multi;
left_num-=y*multi;
}
return result;
}
int main()
{
printf("%d/n”,div(10,3));
return 0;
}
程序输出结果:
3
引申1:如何只用逻辑运算实现加法运算?
实现两个正整数相加,一般直接使用加号运算符即可。考虑到题目中的要求,与上例中的方法二类似,也可以通过移位操作符来进行正整数的加法运算。例如,5与7求和,转换为二进制求和为101与111求和,其二进制结果为1100。对于二进制的加法而言,1+1=0,1+0=1,0+1=1,0+0=0,通过对比位运算中的异或方法,不难发现,此方法与位运算中的异或类似。那么第一个数值就是:tempNum1=num1^num2;0+0的进位是0,1+0的进位是0,只有1+1的进位有效,该思路与位运算的&运算相似,即只有1&1=1,所以可以先num1&num2,由于进位是进到高一位的,与<<运算很相似,同时num1和num2相互&之后,如果结果为0,那么就不存在进位,运算完成,所以可以用递归的思想实现。程序示例如下所示:
#include<stdio.h>
int add(int num1,int num2)
{
if(0==num2)
return num1;
int sumTemp=num1^num2;
int carry=(num1&num2)<<1;
return add(sumTemp,carry);
}
int main()
{
printf("%d/n",add(100,200));
return 0;
}
程序输出结果:
300
将递归思想转换为非递归思想之后,就成了另外一种思路,程序示例如下:
#include<stdio.h>
int add(int num1,int num2)
{
int sum=0;
int num3=0;
int num4=0;
while((num1&num2)>0)
{
num3=num1^num2;
num4=num1&num2;
num1=num3;
num2=num4<<1;
}
sum=num1^num2;
return sum;
}
int main()
{
printff(%d/n",add(100,200));
return 0;
}
程序输出结果:
300
引申2:如何只用逻辑运算实现乘法运算?
先看一个实例:1011*1010,因为二进制运算的特殊性,可以将该乘法运算表达式拆分为两个运算,1011*0010与1011*1000的和,而对于二进制的运算,左移1位,等价于乘以0010,左移3位,等价于乘以1000,所以两者的乘积为10110与1011000的和,即为1101110。
因而乘法可以通过一系列移位和加法完成。最后一个1可通过b&~(b-1)求得,可通过b&(b-1)去掉,为了高效地得到左移的位数,可提前计算一个map,算法如下:
#include<iostream>
#include<map>
using namespace std;
int multiply(int a,int b)
{
bool neg=(b<0);
if(b<0)
b=-b;
int sum=0;
map<int,int>bit_map;
for(int i=0;i<32;i++)
bit_map.insert(pair<int,int>(1<<i,i));
while(b>0)
{
int last_bit=bit_map[b&~(b-1)];
sum+=(a<<last_bit);
b&=b-1;
}
if(neg)
sum=-sum;
return sum;
}
int main()
{
prinff("%d/n",multiply(3,5));
return 0;
}
程序输出结果:
15
函数是程序的基本组成单位,利用函数,不仅能够实现程序的模块化,而且简单、直观,能极大地提高程序的易读性和可维护性,所以将程序中的一些计算或操作抽象成函数以供随时调用,理解函数的执行原理以及应用是一个优秀程序员应该具备的基本能力。