位運算實現加減乘除四則運算(Java)

本文是繼《一文了解有趣的位運算》的第二篇文章.html

咱們知道,計算機最基本的操做單元是字節(byte),一個字節由8個位(bit)組成,一個位只能存儲一個0或1,其實也就是高低電平。不管多麼複雜的邏輯、龐大的數據、酷炫的界面,最終體如今計算機最底層都只是對0101的存儲和運算。所以,瞭解位運算有助於提高咱們對計算機底層操做原理的理解。java

1、加法

兩個二進制數異或運算的結果是不考慮進位時的結果,算法

兩個二進制數與運算的結果中含有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;
}

2、減法

與加法的思路一致,只不過減去一個數等於加一個數的相反數。遞歸

例如: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));
}

3、乘法

若是沒有思路,能夠先在紙上筆算二進制乘法的過程:

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;
}

4、除法

最簡單的除法實現就是不停的用除數去減被除數,直到被除數小於除數時,此時所減的次數就是咱們須要的商,而此時的被除數就是餘數。

惟一須要注意的就是商的符號和餘數的符號。商的符號肯定方式也乘法是同樣,即同號爲正,異號爲負。而餘數的符號和被除數的符號是同樣的。

和簡單的乘法實現同樣,這裏咱們要先對兩數的絕對值求商,求餘數。最後再肯定符號。

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};
}
相關文章
相關標籤/搜索