初等數學題解:化乘除爲加減

===》點我返回目錄《===

我們知道四則混合運算加減乘除是算數的基礎,這裏面乘和除又可以通過加減來實現,所以加減是更爲基本的運算。這些我們小學就知道了,人類是這麼走過來的,計算機也是這麼走過來的。

我們先來實現兩個數的乘法,a*b,按照定義就是把a自加b次,如3*4,就是把3自己加自己總共加四次。

程序如下:

def multiply(a,b):

    if a==0 or b==0:

        return 0

    elif a==1:

        return b

    elif b==1:

        return a

    else:

        r = 0

        while b>=1:

            r = r + a

            b = b - 1

        return r

print(multiply(123,12))

程序的核心就是那個while循環,將a自加b次。

你肯定看出來了,上面的程序只能計算正整數,負數算不對。所以我們再考慮一下正負數。有四種情況:+ +,+ -,- +,- -。對這四種情況,我們得出它的結果的符號,然後化爲正數進行計算。程序如下:

def multiply(a,b):

    sign = 1  #default to positive

    if a>0 and b>0:

        sign = 1

    elif a>0 and b<0:

        sign = -1

        b = 0 - b  # set b to positive

    elif a<0 and b>0:

        sign = -1

        a = 0 - a  # set a to positive

    elif a<0 and b<0:

        sign = 1

        a = 0 - a  # set a to positive

        b = 0 - b  # set b to positive

        

    if a==0 or b==0:

        return 0

    elif a==1:

        return b

    elif b==1:

        return a

    else:

        r = 0

        while b>=1:

            r = r + a

            b = b - 1

        if sign == 1:  # positive

           return r

        else: #negative

            return 0 - r

好。我們通過加法實現了乘法,循環這麼多次,很笨很笨,但是確實是能幹活兒。同樣我們也可以用減法實現除法。

我們再琢磨一下,如果b是實數就算不對了,因爲有小數部分,而上面的循環只對整數有效。實際上實數是由兩個整數中間加點組成的,所以還是可以處理。有點複雜,暫不介紹。

好奇的你又會開始問了,計算機內部真的是這麼幹的嗎?答案是部分是,實際上,計算機內部是通過加法和移位操作聯合起來實現乘法的,爲了處理負數和實數,還需要用到補碼和規範化存儲規範。後面我們會講到。

接下來看一下移位操作怎麼極大地加快了計算。小學學習的就是這個過程。

舉個例子125*103,我們上面的程序需要循環103次,看我們怎麼移位加速:

按照十進制的定義,103 = 1*102+0*101+3*100。這樣只要把125移位幾次再相加即可。分成三部分:

1*102 將125移位2次,再乘1,得到12500

0*101  0值,不計算

3*100 將125移位0次,再乘3,得到375

然後把三部分相加12500+0+375=12875。

同樣得到結果,只用了三次移位外加三次循環。

其實,Python裏面是有移位操作的<<。但是操作的結果有點跟想的不一樣,你可以試一下45<<,結果會返回90,而不是450,你會有一點吃驚。原因是因爲計算機內部用二進制表示,左移一次等效於乘2。我們可以自己模擬寫一個shift函數:

def lshift(a,n):

    r = a

    while n>=1 :

        r = r * 10

        n -= 1

    return r

這個函數將一個數乘10,100,1000,相當於移位n次。本程序實現用到了乘法,這個只是示意,實際計算機裏面移位是一個獨立的基本運算。

這裏我們還用到了一個新的表達 n -= 1,這是一個縮寫,作用等同於 n = n-1,但是執行效率可能會更高(依賴於具體的實現),起碼看起來更加專業。

再用這個lshift()改寫乘法程序:

def multiply(a,b):

    sign = 1

    if a>0 and b>0:

        sign = 1

    elif a>0 and b<0:

        sign = -1

        b = 0 - b

    elif a<0 and b>0:

        sign = -1

        a = 0 - a

    elif a<0 and b<0:

        sign = 1

        a = 0 - a

        b = 0 - b

        

    if a==0 or b==0:

        return 0

    elif a==1:

        return b

    elif b==1:

        return a

    else:

        r = 0 #result

        s = str(b) #convert to string

        for i in range(0,len(s)): # for each digit in the integer string

            n = int(s[len(s)-i-1]) # get the digit from right to left

            c=lshift(a,i) #shift

            while n>=1:

                r = r + c

                n = n - 1

        if sign == 1:

           return r

        else:

            return 0 - r

程序的關鍵部分是那個嵌套循環:  

      for i in range(0,len(s)): # for each digit in the integer string

            n = int(s[len(s)-i-1]) # get the digit from right to left

            c=lshift(a,i) #shift

            while n>=1:

                r = r + c

                n = n - 1

這裏我們看到了一個for語句,這是一個固定次數的循環,次數就是數字串的位數,如對103,循環次數就是3。i的取值依次爲0,1,2。之後,根據位置從右往左拿到該位置上的數字,即程序語句:n = int(s[len(s)-i-1]),如對103,第一次i爲0,那麼就是拿的3-0-1=2這個位置的數字,這個位置是103的最右邊一位(記得編號是從0開始的,所以最後一位也就是第三位的位置編號爲2)。拿到該數字後,先把被乘數移位,移i次,比如對103中的3,就移位0次,相當於不動,對1就要移位兩次。然後拿着這個移位後的數乘以n(通過加循環n次實現),即對103中的3,我們把125循環三次相加得到375,對於103中的0,我們不計算,對於103中的1,我們把12500循環1次相加得到12500,所以最後的結果爲12875。拿出紙和筆,自己手工跟蹤一遍。

順便提一下,while循環裏的r=r+c, n=n-1寫成r += c, n -= 1更加顯得像是高手。

測試一下print(multiply(125,103))。

再解釋一下二進制下的乘法實現,爲以後講計算機內部實現做點準備。

我們已經知道,計算機內部用二進制表示數,101010...,其數值等於k*2n+K*2n-1+...+k*20。

所以a*b就相當於a*k*2n+a*K*2n-1+...+a*k*20,即把a移位後累加。二進制的好處是它只有0和1兩個數字,所以不用考慮別的,直接移位或者不移位就可以了(十進制裏面還要考慮個位數的乘法)。

試一下6*5,6的二進制表示爲110,5的二進制表示爲101,我們計算110*101的值:

  1. 101的右邊第一位爲1,將110左移0位得到110
  2. 101的右邊第二位爲0,因此不計算(結果當成0)
  3. 101的右邊第三位爲1,將110左移2位得到11000
  4. 把1,2,3三步的結果相加:11000+0+110=11110,該結果就是30。

除法類似的,是通過減法和移位操作實現的,我們看一個例子。

我們看815/23,手工按照這幾步從高位到低位做:

  1. 先用8除23,商爲0,餘數爲8;
  2. 餘數8左移,得到80,再加上第二位1,得到81,除23,商爲3,餘數爲12;
  3. 餘數12左移,得到120,再加上第三位5,得到125,除23,商爲5,餘數爲10。

計算完畢,得到商爲035,即35,餘數爲10。

程序如下:

def lshift(a,n):

    r = a

    while n>=1 :

        r = r * 10

        n -= 1

    return r



def divide(a,b):

    sign = 1

    if a>0 and b>0:

        sign = 1

    elif a>0 and b<0:

        sign = -1

        b = 0 - b

    elif a<0 and b>0:

        sign = -1

        a = 0 - a

    elif a<0 and b<0:

        sign = 1

        a = 0 - a

        b = 0 - b

        

    if a==0:

        return 0

    elif b==1:

        return a

    else:

        result = 0

        remaining = 0

        s = str(a)

        for i in range(0,len(s)): # for each digit in a

            n = int(s[i]) # get the digit from left to right

            divident = lshift(remaining,1)+n

            if divident>=b:

                #divide

                count=0

                while divident>=b:

                    count = count + 1

                    divident = divident - b

                result = lshift(result,1) + count

                remaining = divident

            else:

                result = lshift(result,1) + 0

                remaining = divident

                

        if sign == 1:

            return result

        else:

            return 0 - result

測試print(divide(815,23))。你們仿照乘法的過程自己把除法跟蹤一遍。

二進制除法也是同樣的原理,也因爲只有0,1兩個數,所以比十進制簡單,不用除個位數,只需要比大小,大就是1,小就是0。

我們試一下85/6,用二進制表示爲1010101/110,按照如下步驟操作:

  1. 先拿最高位,1,跟110比大小,小,商爲0,餘數爲1;
  2. 餘數左移,爲10,再加上第二位數字0,得數還是10,跟110比大小,小,商爲0,餘數爲10;
  3. 餘數左移,爲100,再加上第三位數字1,得數是101,跟110比大小,小,商爲0,餘數爲101;
  4. 餘數左移,爲1010,再加上第四位數字0,得數是1010,跟110比大小,大,商爲1,餘數爲100;
  5. 餘數左移,爲1000,再加上第五位數字1,得數是1001,跟110比大小,大,商爲1,餘數爲11;
  6. 餘數左移,爲110,再加上第六位數字0,得數是110,跟110比大小,大,商爲1,餘數爲0;
  7. 餘數左移,爲00,再加上第七位數字1,得數是1,跟110比大小,小,商爲0,餘數爲1;

計算完畢,最後的商爲0001110(即14),餘數爲1。

現在我們就知道把四則運算化成了加減運算,以後我們還會進一步將減法化爲加法。這樣從原理上,只需要有加法操作就可以了。萬法歸一。

擴展一下,如果你在中學學得多一點,有可能學到了複數,我們的程序又不能支持了。這裏我們需要一個新的表達,用一個數是不行,複數包含實部和虛部,所以Python用12 + 3j表示。注意這裏用的是j,不是數學中的i,這麼做是爲了遵從電子工程界的慣例。

有了這個知識,我們也是可以寫出程序來實現複數域的運算的。我不演示程序了,你們自己做。

計算機好神奇啊,你肯定會這麼想。它通過簡單數字(0/1)和基本運算疊加累計,最後構成了複雜的運算。但是這麼想並不全對,更準確的說法是計算機很笨而人很聰明,計算機只能做最簡單的操作,而人通過算法一步步讓計算機完成了複雜運算。一直到現在發展出Internet,智能手機,發射火箭,穿越星際空間,傳播視頻,遠程醫療,破譯DNA,發展無人駕駛汽車,但是我們須知循着路徑回溯,一層層破解技術,最後的內核就是一量個簡單極了的操作孤單地擺在我們眼前。正如大千世界,分析到了最後,就只有幾個基本粒子漂浮在虛無縹緲的時空。古來聖賢皆寂寞。

地球上現存的人類只有一種,統一叫做智人(Homo Sapiens)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章