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


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

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM