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的位,所以說掌握位運算很有必要。