最近在LeetCode 上刷題,遇到一個很是有趣的題目,題目的大概意思就是在不使用運算符的狀況下實現兩個數的加法。。。原題點這裏》》》html
說實話,剛看到這題目,我是一臉懵逼的。網絡
後來仔細想一想,若是不能用運算符,那確定是用原始方法了(位運算)。函數
後來,的確也證實個人想法是正確的。不過仍是有種思路沒想到,是參考了網上的。學習
在這裏,我就來講說我所知道的兩個方案。方法low,大牛能夠點擊右上角的×了。。。spa
注:如下討論均基於整數之間的四則運算!部分來自網絡~.net
【加法】指針
方案一(推薦):code
此方法參照計算機的二進制計算htm
分兩步:blog
一:先進行沒有進位的加法運算。可用 a^b;
二:處理進位信息。a&b 可獲得進位的位置信息,而後左移一位,就是兩數相加後的進位信息了。因此能夠用 (a & b) << 1;
而後就是把前面獲得的沒有進位的和加上進位信息了,直到進位爲0爲止。所以代碼能夠這麼寫:
public static int GetSum(int a, int b) { if (b == 0) { return a; } int sum = a ^ b; int carry = (a & b) << 1; int result = GetSum(sum, carry); return result; }
方案二:
此方案在C#中不常使用。將利用指針的偏移來進行加法運算。
先上代碼:
unsafe public static int GetSum_Point(int a, int b) { unsafe { byte* c = (byte*)a; int d = (int)&c[b]; return d; } }
此處附上C#在VS2015中使用指針的方法(傳送門)
例如a=5,b=10
c=(byte*) a,此時c的地址爲0x00000005
c[b] 就是c的地址偏移sizeof(byte)*b
最終獲得了c[b]的地址就是0x0000000f,即經過int強制轉換獲得15 。
【減法】
按理來講,只要加法解決了,後面的運算都是小菜一碟了。本着思考的態度,咱們仍是要想一想怎麼用位運算來實現減法。
總的來講仍是有兩個方案實現的,如下依次來講說。
方案一:
原理其實也是參考計算機計算減法的操做。
這裏須要用到一個叫「補碼」的東西,不懂的同窗點這裏》》》
咱們都知道兩個數的減法能夠看成一個正數和一個負數的加法。照這個思路,咱們能夠這麼寫:
public static int GetMargin(int a, int b) { return GetSum(a, GetSum(~b, 1)); }
方案二:
此方案和【加法】中的方案一相似。都是二進制的計算
咱們分如下幾步來看:(以a - b爲例)
1.若是b 的值爲0,那麼結果顯而易見就是a 了。
2.b 不爲0 的狀況下,咱們仍然先不考慮借位,先將被減數和減數同爲1 的位置去掉。
第一步,找出減數和被減數同爲1 的位置。可以使用 sameNum = a&b; 來實現;
第二步,分別將被減數和減數同爲1 的位置去掉1 ,這裏能夠用 a ^= sameNum; b ^= sameNum;
3.此時,減數和被減數相同位只存在如下三種狀況:
4.經過對被減數、減數和差的分析,很容易就能知道差值應該是被減數和減數的按位或的結果。因而咱們便有:a | b 獲得臨時的結果;
5.此時再考慮借位問題。很明顯只有在減數爲1的狀況下,被減數與之對應的左一位纔會出現借位,因而借位即可以用 b << 1 ; 來表示。
6.再把臨時結果減去借位,直到借位爲0 ,獲得的結果即是最終的結果了。綜上,代碼以下:
public static int GetMargin(int a, int b) { while (b != 0) { // 去掉被減數和減數中同爲1的位 int sameNum = a & b; a ^= sameNum; b ^= sameNum; // 此時,a 和 b 不存在同時爲1 的位 // 0 - 1 和 1 - 0 都爲1 a |= b; // 獲得相減的臨時結果(不考慮借位) b = b << 1; // 減數爲1 時,必有借位 } return a; }
【乘法】
1.先考慮正整數之間的乘法運算。
在二進制中,每向左移動一次,都至關於原始數乘以2。而每一個數據均可以寫成k0×20+k1×21+...+km×2m的形式。所以咱們能夠獲得如下式子:
a x b = ax20xk0 + ax21xk1 + .... + ax2mxkm 其中ki = {0, 1};
所以咱們能夠很容易寫出如下代碼:
public static int GetProduct(int a, int b) { // 1.先只考慮正整數的相乘 int result = 0; for (int bits = 1; bits != 0; bits <<= 1) { if ((bits & b) != 0) { result = GetSum(result, a); } a <<= 1; } return result; }
2.接下來,開始考慮正負號的狀況(考慮溢出的狀況)。
這裏有個簡單的辦法,直接判斷a、b和0 的關係來判斷正負。本着學習的態度(耳熟😀),咱們不使用這種方法。
咱們都知道,在計算機中數據都是以補碼的數據存儲的,其中正數和負數的區別即是最高位是否爲1;(負數的補碼最高位爲1)
因而,咱們即可以引入一個輔助函數,來幫助判斷。
public static int maxNumFlag() { int bitsOfByte = 8; int maxNum = 0x80; int tmp = maxNum; while (tmp != 0) { maxNum = tmp; tmp <<= bitsOfByte; } return maxNum; }
完善後的代碼以下所示:
public static int GetProduct(int a, int b) { // 1.先只考慮正整數的相乘 // 2.考慮正負狀況和溢出問題 int maxNum = maxNumFlag(); int flag_a = 1; if ((maxNum & a) != 0) { flag_a = 0; // 負數 a = GetSum(~a, 1); } int flag_b = 1; if ((maxNum & b) != 0) { flag_b = 0; b = GetSum(~b, 1); } int result = 0; for (int bits = 1; bits != 0; bits <<= 1) { if ((bits & b) != 0) { result = GetSum(result, a); if ((result & maxNum) != 0 || (a & maxNum) != 0) { throw new Exception("數據過大!"); } } a <<= 1; } return (flag_a ^ flag_b) == 0 ? result : GetSum(~result, 1); }
【除法】
看到除法,就想起了減法運算,而後想到一個比較簡單的思路和實現方法。後來發現網上還有一種方法,思路相同又不一樣。貼上來,你們看看~
方案一:
除法沒有溢出,可是有其餘的限定條件,好比除數不能爲「0」。
這裏先說下除法和減法之間的關係。以97÷23=4(餘5)爲例:
也就是 97-23×4=5
:=》97-23-23-23-23=5.
因而,有如下代碼:
public static int GetQuotient(int a, int b) { /*方法一*/ if (b == 0) { throw new Exception("除數不能爲0!!"); } int maxNum = maxNumFlag(); int flag_a = 1; if ((maxNum & a) != 0) { flag_a = 0; // 負數 a = GetSum(~a, 1); } int flag_b = 1; if ((maxNum & b) != 0) { flag_b = 0; b = GetSum(~b, 1); } int index = 1; int tmp = GetMargin(a, b); if (tmp < 0) { return 0; } while (tmp >= b) { tmp = GetMargin(tmp, b); // 最後一次循環後的tmp 即是a/b 的餘數 index = GetSum(index, 1); } return (flag_a ^ flag_b) == 0 ? index : GetSum(~index, 1); }
方案二:
方案二的大致思路以下:
我的仍是以爲這樣比較難理解,可是由於咱們這討論的是位運算,因此仍是貼出來,研究研究。
直接上代碼:
public static int GetQuotient(int a, int b) { /*方法二*/ if (b == 0) { throw new Exception("除數不能爲0!!"); } int maxNum = maxNumFlag(); int flag_a = 1; if ((maxNum & a) != 0) { flag_a = 0; // 負數 a = GetSum(~a, 1); } int flag_b = 1; if ((maxNum & b) != 0) { flag_b = 0; b = GetSum(~b, 1); } int quotient = 0; int backupB = b; while (a >= b) { int tempB = b << 1; int tempQ = 1; while ((tempB <= a) && ((tempB & maxNumFlag()) == 0)) { b = tempB; tempQ <<= 1; tempB <<= 1; } a = GetMargin(a, b); quotient |= tempQ; b = backupB; } if (((maxNum & a) != 0) && (a != 0)) { quotient = GetSum(quotient, 1); } return (flag_a ^ flag_b) == 0 ? quotient : GetSum(~quotient, 1); }