位运算实现加减乘除——看不懂你锤我

实现加减乘除的必要说明

        在进行接下来的操作之前,我们先搞清楚计算机是怎么储存10进制的数字。计算机不像我们人,它只能识别2进制的数字,即只能识别0,1,所有的10进制以及一系列的复杂操作,都是0,1组合起来,在计算机的某些硬件上通过电路表现计算机能看的懂得2进制语言。

        对于计算机来说,它能直接看得懂的操作符号有(这里列出常见的位运算符号)

&(与)                        都为1才1;有0为0
      |(或)                        都为0才0;有1为0
      !(非)                               1变0;0变1
     ^(异或)                        相同为0;不同为1

 这些符号的意思就不解释了,属于最基本的内容。另外,这里最好把^(异或)理解为——无进位的加法最好,或者说以后就把^理解为无进位的加法。

OK了解了这些之后,我们还要明白计算机对于存储的数据不可能是无限大的,以int整型为例:

它的范围为:-2^31 —— 2^31-1 ,即:-2147483648 ——2147483647当超过最大值或者最小值后,数据会溢出。对于非负:0—— 2^31-1 ;对于负数-2^31——-1。🆗了解了这么多就可以开始我们主要的表演了——位运算实现加减乘除。

位运算的加法

        我们在进行10进制的加法运算的时候,逢10进1,同样的我们2进制也可以这么模拟10进制来进行加法运算。

        上面我们介绍了可以把^运算理解成不进位的加法,那么是不是我们再用另一个变量处理进位后的结果,再把两者结合在一起是不是就实现了模拟加法运算呢?很对,我们位运算的加法就是这么个大体思想。

        首先:我们用变量answer处理两个数相加不进位的结果,异或^实现;

        然后:再处理他们的进位的数据;

        最后:整合;

以42,2为例:

        首先他们的状态是这样:

第一步:一开始用answer处理两个数异或后的结果——注意这只是处理每个位上没进位后的结果 

从图片看出(从右往左,从0位开始)a的第一位和b的第一位,应该进位。

第二步:也就是说a的第二位与b的第二位加起来结果应该为1,可以把a&b得到的结果再向左移动一位,就完成了进位操作(与运算只关心是否都为1,这正好也是进位的条件)。

最后再把answer ^ 第二步得到的结果 = 新结果,如此循环往复,直到进位的值为0。

int add(int a,int b)
{
	int answer=a;    

    //如果b==0则不需要进行加法
	while (b!=0)
	{
		answer=a ^ b;        //处理没有进位的加法
		b = (a & b) << 1;    //处理进位信息
		a = answer;          //接着下一轮循环
 	}	
return answer;
} 

位运算的减法

减法就很好说了,减法就是加其的相反数,而一个数的相反数可以先取反再加一得到。

-a=~a+1;

//相反数
int neg(int a)
{
	return add(~a,1); 
}

//位运算减法
int sub(int a,int b)
{
	return add(a,neg(b));
}

位运算的乘法

乘法也是模拟我们10进制的竖式乘法。

  这个是10进制的数相乘得到的结果;其实再细分可以有如下步骤:一个乘数向左移动;一个乘数向右移动;直到向右移动的数为零则结束移动,并把所有得到的结果相加则为答案。 

  模拟2进制乘法也一样,而且更简单,因为不用担心两数相乘会进位的情况(1*1是最大的可能) ,最后也和10进制一样,只要有个数字为0,结束移动,并且把得到的数字相加(用前面add函数)。

int mutl(int a,int b)
{
	if (a<0 && b<0) {a=neg(a);b=neg(b);}    //如果都为负数,则都转化位正数
	if (a<0) {a=neg(a);}               //有一个为正数,则把另一个转化为正数(>>表示带符号移动)
	if (b<0) {b=neg(b);}
	
	int answer=0;
	while (b!=0)
	{
		if((b & 1)!=0)
		{                           //把相乘不是0的结果加起来
			answer=add(answer,a);	//当发现向右移动的那个数最低位!=0,表明相乘的数不为0
                                    //就把得到的结果加起来
		}
		a = a<<1;    //一个数向左移动
		b = b>>1;    //一个数向右移动
	}
return answer;
}

另外说明一下,循环中的代码都是考虑了正数相乘的结果,所以得先把负数转化为正数。(如果是java可以再b = b>>>1表示无符号右移,就不需要考虑正负的情况。>>表示带符号右移,如果是负数右移的话,会在前面补1,改变了数的性质) 

位运算的除法

同样的第一步把负数先转化为正数。以42,10为例,42=10*(2^2)+2,即42/10==4;而2^2==0100

再大一点的数,781,3;781=3*(2^2+2^8)+1 而2^2+2^8=0001 0000 0100,那么可以这样:定义一个标记变量answer=0;把被除数分别向右移动30——0位(为什么不是31——1位,因为最高位管符号),再看在向右移动的过程中:如果有移动后的数字>=除数(说明除数是被除数的一个组成),则把answer相应的位数标为1(用answer ^ (1 << i)实现标记),且把被除数减去移动后的数字作为新的被除数,如此循环下去,直到除数大于被除数,则退出循环;若移动后的数字>除数,则继续往后移动,一直找到循环结束还没有找到移动后的数字>=除数,则退出循环。

 这里比较难以理解的是answer第i位置1与a=a-b<<i。慢慢分析,a=a-b<<i的意思就是把被除数减去移动后的数字作为新的被除数。answer位置为1,来细说。

 

这是answer在这个过程中要完成的事情,至于说answer是如何把0变1的,那就又用到了^运算。

当发现满足a>=b的条件时,此时answer第i位为1,则可以用answer  ^ (1 << i) 来实现。这就实现了answer的标记。

至此是不是以为位运算圆满结束了,并没有,在考虑除法的时候还有边界情况没有考虑,或者这么说,我们上面实现的位运算除法只是满足与非负数与负数的运算,负数那部分并没有包括INT_MIN情况,即int类型的最小值情况。

所以除法还得加上一些边界。


int divide(int a,int b)
{
	if (a==INT_MIN && b==-1) return INT_MAX;
	if (a==INT_MIN && b==INT_MIN) return 1;
	if (a!=INT_MIN && b==INT_MIN) return 0;
	if (a!=INT_MIN && abs(a)<abs(b)) return 0; 	
	
	if (a>0)
	{
		return divi(a,b);
	}
	if (a<0)
	{
		a = b>0 ? a+b : a-b;
	}
int offset=b > 0 ? -1 : 1;
int ans=divi(a,b);
return ans+offset;
	
}

int divi(int a,int b)
{
	int x = a<0 ? neg(a) : a;
	int y = b<0 ? neg(b) : b;
	int answer=0;
	for (int i=30;i>=0;i=sub(i,1))
	{
		if ((x >> i)>=y)
		{
			answer = answer | (1<<i);
			x = sub(x,y<<i);
		}
	}

//异或相同为0 不同为1 
//a<0 b<0 ->异或为0->返回正answer
//a<0 b>=0 ->异或为1->返回负answer
//a>=0 b<0 ->异或为1->返回负answer

return a<0 ^ b<0 ? neg(answer) : answer;
}

分析一下里面比较难懂的代码:

    if (a==INT_MIN && b==-1) return INT_MAX;
	if (a==INT_MIN && b==INT_MIN) return 1;
	if (a!=INT_MIN && b==INT_MIN) return 0;
	if (a!=INT_MIN && abs(a)<abs(b)) return 0; 	

    if (a<0)
	{
		a = b>0 ? a+b : a-b;
	}
int offset=b > 0 ? -1 : 1;
int ans=divi(a,b);
return ans+offset;

前面四个判断:

第一个if a==最小值,b-==-1 两者相除返回最大值 (注意最小值的绝对值与最大值的绝对值不一样,具体原因开头有答案)

第二个if a==最小值,b==最小值 两者相除返回1

第三个if a!=最小值,b==最小值 两者相除返回0 

第四个 if a!=最小值的前提下,当a的绝对值小于b,两者相除返回0

后面那个 if(a<0) 其实更加准确的说法是:if(a<0 && (b!=-1 && b!=INT_MIN)) 

if里面的 语句是当a=最小值,b<0,如果此时再直接调用除法函数,会有溢出的情况(INT_MIN没有其相反数与他对应),所以为了处理这种情况,可以把被除数加上除数的相反值,让其远离最小边界,就是a = b>0 ? a+b : a-b;做的工作。

当你改变了被除数的大小,又要得到正确结果,你最后得变回来,之前是加上除数远离,那么这个时候就得减回来;同理减去除数远离,就得加回来。就是int offset=b > 0 ? -1 : 1;的工作。

最后把变换后的a,b 进行除法运算,即可得出结果。

完整代码

#include <iostream>
using namespace std;
//相反数 
int neg(int a);
//位运算加法 
int add(int a,int b);
//位运算减法 
int sub(int a,int b);
//位运算乘法 
int mutl(int a,int b);
//位运算除法 
int divi(int a,int b);
int divide(int a,int b);

int main()
{
	cout<<divide(INT_MIN,INT_MIN)<<endl;
	cout<<divide(8988,INT_MIN)<<endl;
	cout<<add(100,2627378)<<endl;
	cout<<sub(929293939,233434)<<endl;
	cout<<mutl(47838,283)<<endl;
	cout<<divide(28389349,2)<<endl;
	cout<<divide(INT_MIN,-1)<<endl;
	cout<<divide(-28,4)<<endl;
	cout<<divide(-123,-3)<<endl;
}

int neg(int a)
{
	return add(~a,1); 
}

int add(int a,int b)
{
	int answer=a;
	while (b!=0)
	{
		answer=a ^ b;
		b = (a & b) << 1;
		a = answer;
 	}	
return answer;
} 

int sub(int a,int b)
{
	return add(a,neg(b));
}

int mutl(int a,int b)
{
	if (a<0 && b<0) {a=neg(a);b=neg(b);}
	if (a<0) {a=neg(a);}
	if (b<0) {b=neg(b);}
	
	int answer=0;
	while (b!=0)
	{
		if((b & 1)!=0)
		{
			answer=add(answer,a);	
		}
		a = a<<1;
		b = b>>1; 
	}
return answer;
}

int divide(int a,int b)
{
	if (a==INT_MIN && b==-1) return INT_MAX;
	if (a==INT_MIN && b==INT_MIN) return 1;
	if (a!=INT_MIN && b==INT_MIN) return 0;
	if (a!=INT_MIN && abs(a)<abs(b)) return 0; 	
	
	if (a>0)
	{
		return divi(a,b);
	}
	if (a<0)
	{
		a = b>0 ? a+b : a-b;
	}
int offset=b > 0 ? -1 : 1;
int ans=divi(a,b);
return ans+offset;
	
}

int divi(int a,int b)
{
	int x = a<0 ? neg(a) : a;
	int y = b<0 ? neg(b) : b;
	int answer=0;
	for (int i=30;i>=0;i=sub(i,1))
	{
		if ((x >> i)>=y)
		{
			answer = answer | (1<<i);
			x = sub(x,y<<i);
		}
	}
return a<0 ^ b<0 ? neg(answer) : answer;
}

 附上运行图

 力扣:两数相除https://leetcode.cn/problems/divide-two-integers/

解题代码:

class Solution 
{
public:
int divide(int a,int b)
{
	if (a==INT_MIN && b==-1) return INT_MAX;
		
	if (a==INT_MIN && b==INT_MIN) return 1;

	if (a!=INT_MIN && b==INT_MIN) return 0;
	if (a!=INT_MIN && abs(a)<abs(b)) return 0; 
		
	if (a>0)
	{
		return divi(a,b);
	}

	if (a<0)
	{
		a = b>0 ? a+b : a-b;
	}
	int offset=b > 0 ? -1 : 1;
  int ans=divi(a,b);
  return ans+offset;
	
}

int divi(int a,int b)
{
	
	int x = a<0 ? -a : a;
	int y = b<0 ? -b : b;
	int answer=0;
	for (int i=30;i>=0;i--)
	{
		if ((x>>i) >= y)
		{
			answer = answer | (1 << i);
			x = x - (y<<i);
		}
	}
return (a<0) ^ (b<0) ? -answer : answer;
}

};

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值