1. 程式人生 > >Java位運算實現加減乘除四則運算

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

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

我們知道,計算機最基本的操作單元是位元組(byte),一個位元組由8個位(bit)組成,一個位只能儲存一個0或1,其實也就是高低電平。無論多麼複雜的邏輯、龐大的資料、酷炫的介面,最終體現在計算機最底層都只是對0101的儲存和運算。因此,瞭解位運算有助於提升我們對計算機底層操作原理的理解。

一、加法

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

兩個二進位制數與運算的結果中含有1的位是有進位的位。

0101 + 0001 = 0110為例分析如下:

//計算 0101 + 0001
0101 ^ 0001 = 0100 //異或結果表明,如果不考慮進位,那麼結果為0100
0101 & 0001 = 0001 //與運算結果表明,最低位需要向次低位進1
0001 << 1 = 0010   //與運算結果左移一位,將進位加到高位上

//遞迴計算 0100 + 0010,直到+號右側數字為0

java程式碼:

遞迴


public static int add(int a, int b) {
   if (b == 0) {
       return a;
   } else {
       return add(a ^ b, (a & b) << 1);
   }
}

迴圈


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)。所以,我們只需要求出被減數的相反數即可。

如何求出一個數的相反數?

計算機中儲存的是二進位制的補碼形式,正數的補碼與原碼相同,負數的補碼為對該數的原碼除符號位外各位取反,然後在最後一位加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};
}