首先看一个面试题:
题目:写一个函数,求两个整数的之和,要求在函数体内不得使用+、-、×、÷。
分析:这又是一道考察发散思维的很有意思的题目。当我们习以为常的东西被限制使用的时候,如何突破常规去思考,就是解决这个问题的关键所在。
看到的这个题目,我的第一反应是傻眼了,四则运算都不能用,那还能用什么啊?可是问题总是要解决的,只能打开思路去思考各种可能性。首先我们可以分析人们是如何做十进制的加法的,比如是如何得出5+17=22这个结果的。实际上,我们可以分成三步的:第一步只做各位相加不进位,此时相加的结果是12(个位数5和7相加不要进位是2,十位数0和1相加结果是1);第二步做进位,5+7中有进位,进位的值是10;第三步把前面两个结果加起来,12+10的结果是22,刚好5+17=22。
前面我们就在想,求两数之和四则运算都不能用,那还能用什么啊?对呀,还能用什么呢?对数字做运算,除了四则运算之外,也就只剩下位运算了。位运算是针对二进制的,我们也就以二进制再来分析一下前面的三步走策略对二进制是不是也管用。
5的二进制是101,17的二进制10001。还是试着把计算分成三步:第一步各位相加但不计进位,得到的结果是10100(最后一位两个数都是1,相加的结果是二进制的10。这一步不计进位,因此结果仍然是0);第二步记下进位。在这个例子中只在最后一位相加时产生一个进位,结果是二进制的10;第三步把前两步的结果相加,得到的结果是10110,正好是22。由此可见三步走的策略对二进制也是管用的。
11+11 (第1步结果为00,第二步进位为110.第三步相加110正确)
接下来我们试着把二进制上的加法用位运算来替代。第一步不考虑进位,对每一位相加。0加0与1加1的结果都0,0加1与1加0的结果都是1。我们可以注意到,这和异或的结果是一样的。对异或而言,0和0、1和1异或的结果是0,而0和1、1和0的异或结果是1。接着考虑第二步进位,对0加0、0加1、1加0而言,都不会产生进位,只有1加1时,会向前产生一个进位。此时我们可以想象成是两个数先做位与运算,然后再向左移动一位。只有两个数都是1的时候,位与得到的结果是1,其余都是0。第三步把前两个步骤的结果相加。如果我们定义一个函数AddWithoutArithmetic,第三步就相当于输入前两步骤的结果来递归调用自己。
有了这些分析之后,就不难写出如下的代码了:
int AddWithoutArithmetic(int num1, int num2) { if(num2 == 0) return num1; int sum = num1 ^ num2; int carry = (num1 & num2) << 1; return AddWithoutArithmetic(sum, carry); }
上面的代码和机器内的加法器的原理是一样的。可以看计算机组成原理(白中英)P31.
之前我的系列博客中有这么一道题,求1+2+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字以及条件判断语句(A?B:C)。刚兴趣的读者,可以到http://zhedahht.blog.163.com/blog/static/2541117420072915131422/看看。
怎么判断递归确实是可以结束的?
int add (int a,int b) { int ans; while(b) { //直到没有进位 ans=a^b; b=(a&b)<<1; a=ans; } return a; }
还有一种巧妙的做法;
利用数组下标隐式做加法;
//利用数组下标影式做加法 int add2(int a,int b) { char *c; c=(char *)a; return (int)&c[b]; }
求减法:
我们首先看看补码减法
[x-y]补=[x]补-[y]补=[x]补+【-y]补
从[y]补求[-y]补的法则是:对[y]补包括符号位“求反且最末位加1”,即可得到[-y]补。
数在计算机内部表示就是补码形式的。
//减法:这个和加法一样了,首先取减数的补码,然后相加。 int negative(int a)//取补码 { return add(~a,1); } int sub(int a,int b) { return add(a,negative(b)); }
//正数乘法运算 int Pos_Multiply(int a,int b) { int ans = 0; while(b) { if(b&1) //b最后一位是否为1 ans = Add(ans, a); a = (a<<1); b = (b>>1); } return ans; }
整数除法(正整数):
除法就是由乘法的过程逆推,依次减掉(如果x够减的)y^(2^31),y^(2^30),...y^8,y^4,y^2,y^1。减掉相应数量的y就在结果加上相应的数量。
//除法就是由乘法的过程逆推,依次减掉(如果x够减的)y^(2^31),y^(2^30),...y^8,y^4,y^2,y^1。减掉相应数量的y就在结果加上相应的数量。 int Pos_div(int x,int y) { int ans=0; for(int i=31;i>=0;i--) { // //比较x是否大于y的(1<<i)次方,避免将x与(y<<i)比较,因为不确定y的(1<<i)次方是否溢出 if( (x>>i) >=y ) { ans+=(1<<i); x-= (y<<i); } } return ans; }
例:
5/2
101/10
i=1时,ans+=(1<<1)=2;
x=101-100=1;
i=0; 1>>0 < y 退出
结果i=2;
完整的实现:
// 加减乘除位运算 // 程序中实现了比较大小、加减乘除运算。所有运算都用位操作实现 // 在实现除法运算时,用了从高位到低位的减法 // 具体如下,算法也比较简单,所以没有作注释 #include<iostream> #include<cstdio> using namespace std; int Add(int a, int b) { int ans; while(b) { //直到没有进位 ans = a^b; //不带进位加法 b = ((a&b)<<1); //进位 a = ans; } return a; } //这个和加法一样了,首先取减数的补码,然后相加。 int negtive(int a) //取补码 { return Add(~a, 1); } int Sub(int a, int b) { return Add(a, negtive(b)); } // 判断正负 int ispos( int a ) { //正 return (a&0xFFFF) && !(a&0x8000); } int isneg( int a ) 应该改为bool类型 { //负 return a&0x8000; } bool iszero( int a ) { //0 return !(a&0xFFFF); } //正数乘法运算 int Pos_Multiply(int a,int b) { int ans = 0; while(b) { if(b&1) ans = Add(ans, a); a = (a<<1); b = (b>>1); } return ans; } //乘法运算 int Multiply(int a,int b) { if( iszero(a) || iszero(b) ) return 0; if( ispos(a) && ispos(b) ) return Pos_Multiply(a, b); if( isneg(a) ) { if( isneg(b) ) { return Pos_Multiply( negtive(a), negtive(b) ); } return negtive( Pos_Multiply( negtive(a), b ) ); } return negtive( Pos_Multiply(a, negtive(b)) ); } //除法就是由乘法的过程逆推,依次减掉(如果x够减的)y^(2^31),y^(2^30),...y^8,y^4,y^2,y^1。减掉相应数量的y就在结果加上相应的数量。 int Pos_Div(int x,int y) { int ans=0; for(int i=31;i>=0;i--) { //比较x是否大于y的(1<<i)次方,避免将x与(y<<i)比较,因为不确定y的(1<<i)次方是否溢出 if((x>>i)>=y) { ans+=(1<<i); x-=(y<<i); } } return ans; } //除法运算 int MyDiv( int a, int b ) { if( iszero(b) ) { cout << "Error" << endl; exit(1); } if( iszero(a) ) return 0; if( ispos(a) ) { if( ispos(b) ) return Pos_Div(a, b); return negtive( Pos_Div( a, negtive(b)) ); } if( ispos(b) ) return negtive( Pos_Div( negtive(a), b ) ); return Pos_Div( negtive(a), negtive(b) ); } // 比较两个正数的大小(非负也可) int isbig_pos( int a, int b ) { //a>b>0 int c = 1; b = (a^b); if( iszero(b) ) return 0; while( b >>= 1 ) { c <<= 1; } return (c&a); } // 比较两个数的大小 int isbig( int a, int b ) { //a>b if( isneg(a) ) { if( isneg(b) ) { return isbig_pos( negtive(b), negtive(a) ); } return 0; } if( isneg(b) ) return 1; return isbig_pos(a, b); }
判断负:
int isneg( int a ) { //负 return a&0x8000; }
如果a为负,前面为1111则& 1000变成1,为true,为0111,&1000为0,非负。
如果是0,则还是返回true。
// 判断正负 int ispos( int a ) { //正 return (a&0xFFFF) && !(a&0x8000); }
前面a&0xffff是为了防止0,
--------------------------------------------------
加减乘除位运算
// 程序中实现了比较大小、加减乘除运算。所有运算都用位操作实现
#include <iostream> using namespace std; // 加法 int add( int a, int b ) { int c; while( c = (a&b) ) { a = (a^b); b = (c<<1); } return (a^b); } // 求补码 int rev( int a ) { return add((~a), 1); } // 判断正负 int ispos( int a ) { // 正 return (a&0xFFFF) && !(a&0x8000); } int isneg( int a ) { // 负 return a&0x8000; } int iszero( int a ) { // 0 return !(a&0xFFFF); } // 比较两个正数的大小(非负也可) int isbig_pos( int a, int b ) { // a>b>0 int c = 1; b = (a^b); if( iszero(b) ) return 0; while( b >>= 1 ) { c <<= 1; } return (c&a); } // 比较两个数的大小 int isbig( int a, int b ) { // a>b if( isneg(a) ) { if( isneg(b) ) { return isbig_pos( rev(b), rev(a) ); } return 0; } if( isneg(b) ) return 1; return isbig_pos(a, b); } // 减法 int sub( int a, int b ) { return add(a, rev(b)); } // 正数乘法 int pos_mul( int a, int b ) { int c = 0x8000; int re = a; while( (c>>=1) && (!(b&c)) ); while( c >>= 1 ) { re <<= 1; if( c&b ) re = add(re, a); } return re; } // 乘法 int mul( int a, int b ) { if( iszero(a) || iszero(b) ) return 0; if( ispos(a) && ispos(b) ) return pos_mul(a, b); if( isneg(a) ) { if( isneg(b) ) { return pos_mul( rev(a), rev(b) ); } return rev( pos_mul( rev(a), b ) ); } return rev( pos_mul(a, rev(b)) ); } // 正数除法 int pos_div( int a, int b) { int re = 0, temp = b; if( isbig_pos(b, a)) return 0; do{ temp <<= 1; }while( !isbig_pos(temp, a) ); while( isbig_pos(temp, b) ) { re <<= 1; temp >>= 1; if( !isbig_pos(temp, a)) { a = sub(a, temp); re = add(re, 1); } } return re; } // 除法 int idiv( int a, int b ) { if( iszero(b) ) { cout << "error" << endl; exit(1); } if( iszero(a) ) return 0; if( ispos(a) ) { if( ispos(b) ) return pos_div(a, b); return rev( pos_div( a, rev(b)) ); } if( ispos(b) ) return rev( pos_div( rev(a), b ) ); return pos_div( rev(a), rev(b) ); } int main () { int a, b; while( cin >> a >> b) { if(isbig(a,b)&&(a<=b)) cout << "big error" << endl; if(add(a,b) != (a+b)) cout << "add error" << endl; if(sub(a,b) != (a-b)) cout << "sub error" << endl; if(mul(a,b) != (a*b)) cout << "mul error" << endl; if(idiv(a,b) != (a/b)) cout << "div error" << endl; } return 0; } --
更多:http://blog.sina.com.cn/s/blog_6375752c0100vh0s.html
1. 题目描述
如何使用位操作分别实现整数的加减乘除四种运算?
2. 解决方案
需要熟练掌握一些常见功能的位操作实现,具体为:
<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
(1) 加法实现
可以很容易地用“异或”和“或”操作实现整数加法运算:对应位数的“异或操作”可得到该位的数值,对应位的“与操作”可得到该位产生的高位进位,如:a=010010,b=100111,计算步骤如下:
第一轮:a^b=110101,(a&b)<<1=000100, 由于进位(000100)大于0,则进入下一轮计算,a=110101,b=000100,a^b=110001,(a&b)<<1=001000,由于进位大于0,则进入下一轮计算:a=110001,b=001000,a^b=111001,(a&b)<<1=0,进位为0,终止,计算结果为:111001。
代码如下:
1
2
3
4
5
6
7
8
9
10
|
int
add(
int
a,
int
b) {
int
carry, add;
do
{
add = a ^ b;
carry = (a & b) << 1;
a = add;
b = carry;
}
while
(carry != 0);
return
add;
}
|
(2) 减法实现
减法可很容易地转化为加法:a - b = a + (-b) = a + (~b + 1 )
代码如下:
1
2
3
|
int
subtract(
int
a,
int
b) {
return
add(a, add(~b, 1));
}
|
(3) 乘法实现
先看一个实例:1011*1010:
1
2
3
4
5
6
7
|
1011
* 1010
----------
10110 < 左移一位,乘以0010
+ 1011000 < 左移3位,乘以1000
----------
1101110
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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;
}
|
(4) 除法实现
乘法可很容易转化为减法操作,主要思想与乘法实现类似,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
int
divide(
int
a,
int
b) {
bool
neg = (a > 0) ^ (b > 0);
if
(a < 0)
a = -a;
if
(b < 0)
b = -b;
if
(a < b)
return
0;
int
msb = 0;
for
(msb = 0; msb < 32; msb++) {
if
((b << msb) >= a)
break
;
}
int
q = 0;
for
(
int
i = msb; i >= 0; i--) {
if
((b << i) > a)
continue
;
q |= (1 << i);
a -= (b << i);
}
if
(neg)
return
-q;
return
q;
}
|
原创文章,转载请注明: 转载自新书《程序员面试笔试宝典》官网
本文链接地址: 位操作实现加减乘除四则运算