您的位置:首页 > 其它

用位运算实现四则运算之加减乘除

2015-06-18 09:05 543 查看
转自:http://www.cnblogs.com/dandingyy/archive/2012/10/29/2745570.html 

以及:http://blog.csdn.net/luckyxiaoqiang/article/details/6886489

  我认为原文中有错误的地方,被我改动了

^: 按位异或;&:按位与; | :按位或;~按位取反

计算机系统中,数值一律用补码来表示:因为补码可以使符号位和数值位统一处理,同时可以使减法按照加法来处理。

对补码做简单介绍:数值编码分为原码,反码,补码,符号位均为0正1负。

正数补码:与原码相同。 例如,+9的原码和补码都是00001001

下面两句感觉原文中作者总结错了,给我给更正了下:

负数原码 -> 补码: 数值位取反加1。

负数补码 -> 原码: 对该补码的数值位逐位 取反加1。

已知一个数的补码,求原码的操作分两种情况: (1)如果补码的符号位为“0”,表示是一个正数,所以补码就是该数的原码。 (2)如果补码的符号位为“1”,表示是一个负数,求原码的操作可以是:符号位为1,其余各位取反,然后再整个数加1。 例如,已知一个补码为11111001,则原码是10000111(-7):因为符号位为“1”,表示是一个负数,所以该位不变,仍为 “1”;其余7位1111001取反后为0000110;再加1,所以是10000111。

补码 的绝对值(称为真值):正数的真值就是本身,负数的真值是各位(包括符号位)取反加1(即变成原码并把符号位取反)。

b -> -b : 各位(包括符号位)取反加1。

 int a=7;a在32位的计算机中表示如下:00000000 00000000 00000000 00000111

则~a在32位的计算机中表示如下:        11111111 11111111 11111111 11111000;

为补码,转换为原码则为                         10000000 00000000 00000000 00001000 也就是-8

加法运算:将一个整数用二进制表示,其加法运算就是:相异(^)时,本位为1,进位为0;同为1时本位为0,进位为1;同为0时,本位进位均为0.

所以,不计进位的和为sum = a^b,进位就是arr = a&b,(与sum相加时先左移一位,因为这是进位)。完成加法直到进位为0.

减法运算:a-b  = a+(-b)  根据补码的特性,各位取反加1即可(注意得到的是相反数,不是该数的补码,因为符号位改变了)

(上面用二进制实现的加减法可以直接应用于负数)

乘法运算:原理上还是通过加法计算。将b个a相加,注意下面实际的代码。

除法运算:除法运算是乘法的逆。看a最多能减去多少个b,

加法运算代码如下:

//递归版本的加法实现
int Add(int a, int b)
{
return b ? Add(a^b, (a&b)<<1) : a;
}

//该为迭代版本
int Add_iter(int a, int b)
{
int ans;
while(b)//直到没有进位
{
ans = a^b;//不带进位加法
b = (a&b)<<1;//进位
a = ans;
}
return ans;
}


求相反数:

//求a的相反数:将各位取反加一
int negative(int a)     //get -a
{
return Add(~a, 1);  //~a表示a的数值位取反
}
//也可以这样求a的相反数
int negative(int b)
{
int i;
for( i = 1; i && ((b & i) ==0 ); i <<= 1)//先找到b的二进制位数中从右边数第一个不为0的位,
;
for( i=i<< 1;i ; i =i<<1 ) //从下一位开始,从右向左,凡是b中为1的均置位0,为0的置为1;
b ^= i;
return b;
}
//如int b=6,则b的二进制表示为:00000000 00000000 00000000 00000110
则~b的二进制表示为:	      11111111 11111111 11111111 11111001
通过上面的第二个negative(int b)求值后,b的二进制表示为:11111111 11111111 11111111 11111010
减法运算:
//减法运算
int Minus(int a, int b)
{
return Add(a, negative(b));
}


判断数字是否为正数,负数,或0:
//判断是否是负数
int isneg(int a)
{
return a & 0x8000;//注意此处是0x80000000,网上有些地方是0x8000,结果会得出错误的结果
}
//判断是否是0
int iszero(int a)
{
return !(a & 0xFFFF);//注意此处是0xFFFFFFFF,网上有些地方是0xFFFF,结果会得出错误的结果
}
//判断是否是正数
int ispos(int a)
{
return (a&0xFFFF) && !(a&0x8000);//非0且非负
}

用位操作求两个数的平均值:

(x&y)+((x^y)>>1)如数x:01010,y:01100。x&y为01000,即取x,y中对应位都为1的位,并将结果的对应位置1,相当于取得了都为1的位相加的一半。 x^y结果为00110,即取x,y中对应位只有一个为1的位,并将结果的对应位置1,由于最终结果是平均值,故除以2(用右移1位实现)。以上两部分结果相加可得两数的平均值。

乘法运算:

乘法就是将其中的一个乘数写成(2^0)*k0 + (2^1)*k1 + (2 ^2)*k2 + ... + (2^31)*k31,其中ki为0或1,然后利用位运算和加法就可以了。

int Mul(int a,int b) //写的不是很完整,有待做溢出检测
{
int ans = 0;
for(int i = 1; i; i <<= 1, a <<= 1)
if(b & i)
ans += a;
return ans;
}


除法运算:

原理:除法就是由乘法的过程逆推,依次减掉(如果x够减的)y^(2^31),y^(2^30),...y^8,y^4,y^2,y^1。减掉相应数量的y就在结果加上相应的数量。 

int divide(int dividend, int divisor) {
if(isZero(divisor))//若分母为0,则返回正数最大值2147483647
return (numeric_limits<int>::max)();
if(isZero(dividend))//若分子为0,则返回0值
return 0;

if(dividend==(numeric_limits<int>::min)()&&divisor==-1)//防止溢出,当最小负数值-2147483648除以-1时,返回正数最大值2147483647
return (numeric_limits<int>::max)();

int flag=-1;//结果符号位
if((isNeg(dividend)&&isNeg(divisor))||(!isNeg(dividend)&&!isNeg(divisor)))//符号为正
flag=1;

unsigned int x=(unsigned int)abs(dividend);//求绝对值
unsigned int y=(unsigned int)abs(divisor);
unsigned int re=0;
for(int k=31;k>=0;--k)
{
if((x>>k)>=y)<span class="comment">/比较x是否大于y的(1<<i)次方,避免将x与(y<<i)比较,因为不确定y的(1<<i)次方是否溢出</span>
{
re+=1<<k;
x-=y<<k;
}
}
if(re>(numeric_limits<int>::max)()&&flag==-1)//若结果为负数,且大于整数表示范围,这种情况出现在-2147483648除以1上
return (numeric_limits<int>::min)();
else
return flag*re;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  位运算