最近在LeetCode 上刷題,遇到一個非常有趣的題目,題目的大概意思就是在不使用運算符的情況下實現兩個數的加法。。。原題點這里》》》
說實話,剛看到這題目,我是一臉懵逼的。
后來仔細想想,如果不能用運算符,那肯定是用原始方法了(位運算)。
后來,的確也證明我的想法是正確的。不過還是有種思路沒想到,是參考了網上的。
在這里,我就來說說我所知道的兩個方案。方法low,大牛可以點擊右上角的×了。。。
注:以下討論均基於整數之間的四則運算!部分來自網絡~
【加法】
方案一(推薦):
此方法參照計算機的二進制計算
分兩步:
一:先進行沒有進位的加法運算。可用 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.此時,減數和被減數相同位只存在以下三種情況:
-
- 被減數:0 ;減數: 0;差:0;
- 被減數:0 ;減數: 1;差:1;
- 被減數:1 ;減數: 0;差:1;
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); }
方案二:
方案二的大體思路如下:
- 預備工作:置商為0;
- 判斷“被除數>=除數 ”是否成立:
成立,繼續步驟3;
不成立,被除數的值賦給余數,計算結束。 - 備份除數,並設置商分子(一個臨時變量,最終需加到商上面,故暫且如此命名)為1;
對商分子和除數同步向左移位,直到繼續移位將大於被除數時為止; - 從被除數上減去除數,並將商加上商分子。
- 通過備份的除數值還原除數,跳轉到步驟2繼續執行。
個人還是覺得這樣比較難理解,但是因為我們這討論的是位運算,所以還是貼出來,研究研究。
直接上代碼:
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); }
