位运算☞加减乘除运算

29 篇文章 1 订阅

 

1. 加法运算

13 + 9 = 21
// 13--0000 1101
//  9--0000 1001

十进制思想:

  • 不考虑进位,分别对各位数进行相加,结果为sum: 
  • 个位数3加上9为2;十位数1加上0为1; 最终结果为12;
  • 只考虑进位,结果为carry: 
  • 3 + 9 有进位,进位的值为10;
  • 如果步骤2所得进位结果carry不为0,对步骤1所得sum,步骤2所得carry重复步骤1、 2、3;如果carry为0则结束,最终结果为步骤1所得sum.

步骤:(1) 不考虑进位,分别对各位数进行相加:sum = 22; (2) 只考虑进位: 上一步没有进位,所以carry = 0; (3) 步骤2carry = 0,结束,结果为sum = 22. 

不考虑进位,分别对各位数进行相加: <二进制同理考虑>

13 + 9 = 0000 1101 + 000 01001 = 0000 0100

考虑进位: 
有两处进位,第0位和第3位,只考虑进位的结果为: 

carry = 0001 0010

carry == 0, is true or false,不为0,重复步骤1 、2 、3;为0则结束,结果为sum: 
本例中, 
(a)不考虑进位sum = 0001 0110; 
(b)只考虑进位carry = 0; 
(c)carry == 0,结束,结果为sum = 0001 0110  = 22;

结论:十进制的三板斧同样适用于二进制,第一步不考虑进位的加法其实就是异或运算;而第二步只考虑进位就是与运算并左移一位;第三步就是重复前面两步操作直到第二步进位结果为0。

// 递归写法
int add(int num1, int num2)
{
    if(num2 == 0) return num1;
    int sum = num1 ^ num2;
    int carry = (num1 & num2) << 1;
    return add(sum, carry);
}

// 迭代写法
int add(int num1, int num2)
{
    int sum = num1 ^ num2;
    int carry = (num1 & num2) << 1;  
    while(carry != 0)
    {
        int a = sum;
        int b = carry;
        sum = a ^ b;
        carry = (a & b) << 1;  
    }
    return sum;
}

2.减法运算

思想:减法运算转变成加法运算.

减法运算可转变成一个正数加上一个负数,那首先就要来看看负数在计算机中是怎么表示的。

+8在计算机中表示为二进制的1000,那-8怎么表示呢?

很容易想到,可以将一个二进制位(bit)专门规定为符号位,它等于0时就表示正数,等于1时就表示负数。比如,在8位机中,规定每个字节的最高位为符号位。那么,+8就是00001000,而-8则是10001000。这只是直观的表示方法,其实计算机是通过2的补码来表示负数的,那什么是2的补码(同补码,英文是2’s complement,其实应该翻译为2的补码)呢?它是一种用二进制表示有号数的方法,也是一种将数字的正负号变号的方式,求取步骤:

  • 第一步,每一个二进制位都取相反值,0变成1,1变成0(即反码)。

  • 第二步,将上一步得到的值(反码)加1。

 8  ----  00000000 00000000 00000000 00001000   原码,补码,反码
 -8 ----  10000000 00000000 00000000 00001000   原码
          11111111 11111111 11111111 11110111   反码
          11111111 11111111 11111111 11111000   补码

利用的补码可以将数字的正负号变号的功能,这样我们就可以把减法运算转变成加法运算了,因为负数可以通过其对应正数求补码得到。计算机也是通过增加一个补码器配合加法器来做减法运算的,而不是再重新设计一个减法器。

int sub(int num1, int num2)
{
    int subtractor = add(~num2, 1);// 先求减数的补码(取反加一)
    int result = add(num1, subtractor); 
    return result ;
}

3.乘法运算

加法运算的位运算实现,是将乘法运算转换成加法运算,被乘数加上乘数倍的自己。这里还有一个问题,就是乘数和被乘数的正负号问题,我们这样处理,先处理乘数和被乘数的绝对值的乘积,然后根据它们的符号确定最终结果的符号即可。步骤如下: 

  • 计算绝对值得乘积
  • 确定乘积符号(同号为证,异号为负)
int multiply(int num1, int num2)
{ 
    // 取绝对值      
    int multiplicand = num1 < 0 ? add(~num1, 1) : num1;    
    int multiplier = num2 < 0 ? add(~num2 , 1) : num2;// 如果为负则取反加一得其补码,即正数      
    // 计算绝对值的乘积      
    int product = 0;    
    int count = 0;    
    while(count < multiplier) {        
        product = add(product, multiplicand);        
        count = add(count, 1);// 这里可别用count++,都说了这里是位运算实现加法      
    }    
    // 确定乘积的符号      
    if((num1 ^ num2) < 0) {// 只考虑最高位,如果a,b异号,则异或后最高位为1;如果同号,则异或后最高位为0;            
        product = add(~product, 1);    
    }    
    return product;
}

优化:

int multiply(int num1, int num2) num2
{  
    //将乘数和被乘数都取绝对值 
    int multiplicand = num1 < 0 ? add(~num1, 1) : num1;   
    int multiplier = num2 < 0 ? add(~num2 , 1) : num2;  
     
    //计算绝对值的乘积  
    int product = 0;  
    while(multiplier > 0) {    
        if((multiplier & 0x1) > 0) {// 每次考察乘数的最后一位    
            product = add(product, multiplicand);    
        }     
        multiplicand = multiplicand << 1;// 每运算一次,被乘数要左移一位    
        multiplier = multiplier >> 1;// 每运算一次,乘数要右移一位(可对照上图理解)  
    }   
    //计算乘积的符号  
    if((num1 ^ num2) < 0) {    
        product = add(~product, 1);  
    }   
    return product;
}

4.除法运算

除法运算可以转换成减法运算,即不停的用除数去减被除数,直到被除数小于除数时,此时所减的次数就是我们需要的商,而此时的被除数就是余数。这里需要注意的是符号的确定,商的符号和乘法运算中乘积的符号确定一样,即取决于除数和被除数,同号为证,异号为负;余数的符号和被除数一样。 

int divide(int num1, int num2)
{    
    // 先取被除数和除数的绝对值    
    int dividend = num1 > 0 ? num1 : add(~num1, 1);    
    int divisor = num2 > 0 ? num2 : add(~num2, 1);    
    int quotient = 0;// 商    
    int remainder = 0;// 余数    
    // 不断用除数去减被除数,直到被除数小于被除数(即除不尽了)    
    while(dividend >= divisor)// 直到商小于被除数  
    {      
        quotient = add(quotient, 1);        
        dividend = substract(dividend, divisor);    
    }    
    // 确定商的符号    
    if((num1 ^ num2) < 0)// 如果除数和被除数异号,则商为负数  
    {
        quotient = add(~quotient, 1);    
    }    
    // 确定余数符号    
    remainder = b > 0 ? dividend : add(~dividend, 1);    
    return quotient;// 返回商
}

优化;

int divide_v2(int num1,int num2) {   
    // 先取被除数和除数的绝对值    
    int dividend = num1 > 0 ? num1 : add(~num1, 1);    
    int divisor = num2 > 0 ? num2 : add(~num2, 1);    
    int quotient = 0;// 商    
    int remainder = 0;// 余数    
    for(int i = 31; i >= 0; i--)
     {
        //比较dividend是否大于divisor的(1<<i)次方,不要将dividend与(divisor<<i)比较,而是用(dividend>>i)与divisor比较,
        //效果一样,但是可以避免因(divisor<<i)操作可能导致的溢出,如果溢出则会可能dividend本身小于divisor,但是溢出导致dividend大于divisor       
        if((dividend >> i) >= divisor)
     {            
            quotient = add(quotient, 1 << i);            
            dividend = substract(dividend, divisor << i);        
        }    
    }    
    // 确定商的符号    
    if((num1 ^ num2) < 0)
    {
        // 如果除数和被除数异号,则商为负数        
        quotient = add(~quotient, 1);    
    }    
    // 确定余数符号    
    remainder = b > 0 ? dividend : add(~dividend, 1);    
    return quotient;// 返回商
}

写在最后: 我们的计算机其实就是通过上述的位运算实现加法运算的(通过加法器,加法器就是使用上述的方法实现加法的),而程序语言中的+ - * /运算符只不过是呈现给程序员的操作工具,计算机底层实际操作的永远是形如0101的位,所以说掌握位运算很有必要。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值