本文是繼《一文了解有趣的位運算》的第二篇文章.html
咱們知道,計算機最基本的操做單元是字節(byte),一個字節由8個位(bit)組成,一個位只能存儲一個0或1,其實也就是高低電平。不管多麼複雜的邏輯、龐大的數據、酷炫的界面,最終體如今計算機最底層都只是對0101的存儲和運算。所以,瞭解位運算有助於提高咱們對計算機底層操做原理的理解。java
兩個二進制數異或運算的結果是不考慮進位時的結果,算法
兩個二進制數與運算的結果中含有1的位是有進位的位。ide
以0101 + 0001 = 0110
爲例分析以下:優化
//計算 0101 + 0001 0101 ^ 0001 = 0100 //異或結果代表,若是不考慮進位,那麼結果爲0100 0101 & 0001 = 0001 //與運算結果代表,最低位須要向次低位進1 0001 << 1 = 0010 //與運算結果左移一位,將進位加到高位上 //遞歸計算 0100 + 0010,直到+號右側數字爲0
java代碼:code
遞歸htm
public static int add(int a, int b) { if (b == 0) { return a; } else { return add(a ^ b, (a & b) << 1); } }
循環blog
public static int add2(int a, int b) { int sum = a; while (b != 0) { sum = a ^ b; b = (a & b) << 1; a = sum; } return sum; }
與加法的思路一致,只不過減去一個數等於加一個數的相反數。遞歸
例如:5-1 = 5+(-1)。因此,咱們只須要求出被減數的相反數便可。ip
如何求出一個數的相反數?
計算機中存儲的是二進制的補碼形式,正數的補碼與原碼相同,負數的補碼爲對該數的原碼除符號位外各位取反,而後在最後一位加1。
例如:
1在計算機中的二進制表示爲:0000 0001
-1在計算機中的二進制表示爲:1111 1111
計算過程爲:
-1的原碼:1000 0001
-1的反碼:1111 1110
-1的補碼:1111 1111
其中,由1的原碼(0000 0001)取反可得-1的反碼(1111 1110)
總結,一個數的相反數的求法就是該數每一位取反,末位加一。
java代碼:
public static int minus(int a, int b) { return add(a, add(~b, 1)); }
若是沒有思路,能夠先在紙上筆算二進制乘法的過程:
0101 a × 0110 b ---------------- 0000 0101 0101 + 0000 ---------------- 00011110
梳理下筆算二進制乘法的過程:
初始化乘積結果爲0,依次遍歷數字b的末位 0→1→1→0,當末位爲0時,乘積結果加上0,也就是乘積不變,A左移一位;當末位爲1時,乘積結果加上A,A再左移一位。
如何遍歷數字b的末位呢?
根據前面所學,咱們可使用與運算取一個數的末位,並不斷右移數字b,直到數字b==0,便可結束位移。
須要注意的是正負數的符號問題,此處是先對a、b兩數的絕對值計算其乘積,最後再肯定其符號。
java代碼爲:
public static int multiply(int a, int b) { //將乘數和被乘數都取絕對值 int A = a < 0 ? add(~a, 1) : a; int B = b < 0 ? add(~b, 1) : b; //計算絕對值的乘積 int P = 0; while (B != 0) { if ((B & 1) != 0) { //取乘數的二進制的最後一位,0 or 1 P = add(P, A); } A = A << 1; B = B >> 1; } //計算乘積的符號 if ((a ^ b) < 0) { P = add(~P, 1); } return P; }
最簡單的除法實現就是不停的用除數去減被除數,直到被除數小於除數時,此時所減的次數就是咱們須要的商,而此時的被除數就是餘數。
惟一須要注意的就是商的符號和餘數的符號。商的符號肯定方式也乘法是同樣,即同號爲正,異號爲負。而餘數的符號和被除數的符號是同樣的。
和簡單的乘法實現同樣,這裏咱們要先對兩數的絕對值求商,求餘數。最後再肯定符號。
public static int[] divide(int a, int b) { //對被除數和除數取絕對值 int A = a < 0 ? add(~a, 1) : a; int B = b < 0 ? add(~b, 1) : b; //對被除數和除數的絕對值求商 int C = A; // 餘數C int N = 0; // 商N while (C >= B) { C = minus(C, B); // C-B N = add(N, 1); // N+1 } // 求商的符號 if ((a ^ b) < 0) { N = add(~N, 1); } // 求餘數的符合 if (a < 0) { C = add(~C, 1); } return new int[]{N, C}; }
須要指出的是,這種算法在A很大、B很小的狀況下效率很低,那該如何優化算法減小while循環的次數呢?
不難想到,除法是由乘法的過程逆推而來的。例如 9÷4=2...1,也就是2*4+1=9。假設用9去減4*2,能夠得出結果等於1,由於1小於4,那麼就能夠得出9÷4的商是2,餘數是1。
如何肯定4的倍數是逼近最終結果的關鍵。咱們知道,int 整型有32位,除首位表示符號位,每一位的大小是 [2^0, 2^1, 2^2, , , 2^30],最大的int整數是2^31-1。因此,咱們能夠依次將被除數與2^31, 2^30, ...2^3, 2^2, 2^1, 1相乘,若是除數大於它們的乘積,除數就與之相減,並用相減獲得的餘數繼續做爲除數,直到循環結束。
java代碼:
public static int[] divide(int a, int b) { // 對被除數和除數取絕對值 int A = a < 0 ? add(~a, 1) : a; int B = b < 0 ? add(~b, 1) : b; int N = 0; // 商 N for (int i = 31; i >= 0; i--) { // 未使用A>=(B<<i)進行判斷,由於只有左移B時捨棄的高位不包含1,才至關於該數乘以2的i次方. if ((A >> i) >= B) { // A ÷ 2^i >= B N += (1 << i); // N = N + 2^i A -= (B << i); // A = A - B*2^i } } int C = A; // 餘數C // 求商的符號 if ((a ^ b) < 0) { N = add(~N, 1); } // 求餘數的符號 if (a < 0) { C = add(~C, 1); } return new int[]{N, C}; }