===》點我返回目錄《===
我們知道四則混合運算加減乘除是算數的基礎,這裏面乘和除又可以通過加減來實現,所以加減是更爲基本的運算。這些我們小學就知道了,人類是這麼走過來的,計算機也是這麼走過來的。
我們先來實現兩個數的乘法,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的值:
- 101的右邊第一位爲1,將110左移0位得到110
- 101的右邊第二位爲0,因此不計算(結果當成0)
- 101的右邊第三位爲1,將110左移2位得到11000
- 把1,2,3三步的結果相加:11000+0+110=11110,該結果就是30。
除法類似的,是通過減法和移位操作實現的,我們看一個例子。
我們看815/23,手工按照這幾步從高位到低位做:
- 先用8除23,商爲0,餘數爲8;
- 餘數8左移,得到80,再加上第二位1,得到81,除23,商爲3,餘數爲12;
- 餘數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,跟110比大小,小,商爲0,餘數爲1;
- 餘數左移,爲10,再加上第二位數字0,得數還是10,跟110比大小,小,商爲0,餘數爲10;
- 餘數左移,爲100,再加上第三位數字1,得數是101,跟110比大小,小,商爲0,餘數爲101;
- 餘數左移,爲1010,再加上第四位數字0,得數是1010,跟110比大小,大,商爲1,餘數爲100;
- 餘數左移,爲1000,再加上第五位數字1,得數是1001,跟110比大小,大,商爲1,餘數爲11;
- 餘數左移,爲110,再加上第六位數字0,得數是110,跟110比大小,大,商爲1,餘數爲0;
- 餘數左移,爲00,再加上第七位數字1,得數是1,跟110比大小,小,商爲0,餘數爲1;
計算完畢,最後的商爲0001110(即14),餘數爲1。
現在我們就知道把四則運算化成了加減運算,以後我們還會進一步將減法化爲加法。這樣從原理上,只需要有加法操作就可以了。萬法歸一。
擴展一下,如果你在中學學得多一點,有可能學到了複數,我們的程序又不能支持了。這裏我們需要一個新的表達,用一個數是不行,複數包含實部和虛部,所以Python用12 + 3j表示。注意這裏用的是j,不是數學中的i,這麼做是爲了遵從電子工程界的慣例。
有了這個知識,我們也是可以寫出程序來實現複數域的運算的。我不演示程序了,你們自己做。
計算機好神奇啊,你肯定會這麼想。它通過簡單數字(0/1)和基本運算疊加累計,最後構成了複雜的運算。但是這麼想並不全對,更準確的說法是計算機很笨而人很聰明,計算機只能做最簡單的操作,而人通過算法一步步讓計算機完成了複雜運算。一直到現在發展出Internet,智能手機,發射火箭,穿越星際空間,傳播視頻,遠程醫療,破譯DNA,發展無人駕駛汽車,但是我們須知循着路徑回溯,一層層破解技術,最後的內核就是一量個簡單極了的操作孤單地擺在我們眼前。正如大千世界,分析到了最後,就只有幾個基本粒子漂浮在虛無縹緲的時空。古來聖賢皆寂寞。
地球上現存的人類只有一種,統一叫做智人(Homo Sapiens)。