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