不使用運算符(+、-、*、/) 來進行四則運算(C#)

最近在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.此時,減數和被減數相同位只存在如下三種狀況:

    1. 被減數:0 ;減數: 0;差:0;
    2. 被減數:0 ;減數: 1;差:1;
    3. 被減數: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 + .... + ax2mxk其中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);
        }

方案二:

  方案二的大致思路以下:

    1. 預備工做:置商爲0;
    2. 判斷「被除數>=除數 」是否成立:
      成立,繼續步驟3;
      不成立,被除數的值賦給餘數,計算結束。
    3. 備份除數,並設置商分子(一個臨時變量,最終需加到商上面,故暫且如此命名)爲1;
      對商分子和除數同步向左移位,直到繼續移位將大於被除數時爲止;
    4. 從被除數上減去除數,並將商加上商分子。
    5. 經過備份的除數值還原除數,跳轉到步驟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);
        }
相關文章
相關標籤/搜索