序
這兩天心血來潮, 想熟悉下Eclipse。 偶是寫Python的, 一般都在Ubuntu上用VIM做編輯器, PDB做調試工具, 自帶的python2.7解釋器, git做版本控制。 用着挺爽, 也就沒有搞啥IDE, 但是最近發現要做大項目或工程的話, IDE啥的還是很有必要的。
幾乎把所有支持Py的IDE都嘗試過了, eric用着特別扭(還是比較習慣VIM的便捷操作), 要Python的自動補全居然還要用戶自己配置api文件。。 居然還敢自稱專業Python IDE..太讓人失望了。 wing IDE試用了下,挺不錯的。。 但是要收費。。 想着在Linux環境下還要用破解軟件就覺得特憋屈。 vim -> configure -> make -> make install已成習慣,自然不堪其辱,果斷放棄了。。 IDLE頂多算個編輯器。 還沒VIM功能強大, 完全感覺不到可以拿來做大項目。。
最後還是選擇了Eclipse + Pydev + Vrapper Eclipse強大的工程管理和自動補全,doc功能 + VIM的全鍵盤式操作 = 犀利。。
一直堅持寫些簡單算法練手, 邏輯, coding 雙豐收, 咧呵呵。
正式開始
加法:
先寫了測試代碼:
#!/usr/bin/env python
from hugeoperation import HugeOperation
import unittest
class TestHugeOperation(unittest.TestCase):
def setUp(self):
self.testobj = HugeOperation()
self.testdata = {}
self.testdata['add'] = [
['2000', '1000', '1000'],
['0','0','0'],
['1', '0', '1'],
['0', '1', '-1'],
['337618', '3495', '334123'],
['-219', '-98', '-121'],
['3', '-32', '35'],
['-2085924839766513752338888384931203236916703635113918720651407820138886450957659038931612598272',\
'-1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136',\
'-1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136'],
['1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136',\
'1042962419883256876169444192465601618458351817556959360325703910069443225478828393565899456512',\
'1125899906842624' ]
]
def test_add(self):
for i in self.testdata['add']:
self.assertEqual(i[0], self.testobj.huge_add(i[1], i[2]))
print i,
print ' Passed!\n'
if __name__=='__main__':
unittest.main()
就經驗來說,四則裏面最基本的應該是加法,所以就先設計好加法的測試用列, 給了9組測試數據, 肯定沒考慮全面, 比如邊界值測試,異常輸入什麼的都沒設計。。 畢竟只是練手的東西,要求不要太嚴格了,呵呵
然後開始設計算法:
計算機指令的運算是基於位的二進制運算, 大學老師教過整型有表示範圍, 就算用長整型也是有限的,具體限制與不同語言和不同系統平臺有關。不借助庫的話是無法實現超級大數運算的。 如計算2^258 + 2^512 用一般的整型運算肯定是無法實現的。
我使用字符串來存儲兩個數,然後用一個字符串來存儲結果。
加法比較簡單, 從低位起逐位相加,除10取餘與進位寄存器相加便是該位的值, 進位只有0和1兩種狀態。
實現代碼如下:
#add fun
def _add_operate(self, anum, bnum):
carry = 0
rdigit = 0
rnum = []
while anum or bnum:
adigit = 0
bdigit = 0
if anum:
adigit=anum.pop()
if bnum:
bdigit=bnum.pop()
rdigit = adigit + bdigit + carry
carry = rdigit / 10
rdigit = rdigit % 10
rnum.append(str(rdigit))
if carry:
rnum.append('1')
rnum.reverse()
result = rnum
return result
該函數輸入是兩個整形列表,分別順序存儲了兩個加數。 輸出是一個順序存儲了計算結果的字符型列表 (如果出於對輸入兼容性的角度考慮, 可以在函數最開始對列表元素做強制轉型處理, 這裏爲了簡潔我沒做。)
有兩個地方需要注意: 1. 當算到最後一位仍有進位時, 需要把進位添加到結果的最高位, 當然,也只可能是1.
2. 進位寄存器使用後要歸零, 這個比較容易出bug。
但是一般來說讀入的數據會是兩個字符串, 如我們測試用列設計的那樣, 所以需要一個對輸入初始化的函數, 將字符串轉換爲列表:
def _huge_init(self, a):
'''_huge_init(string)
return list[list[sign],list[num]]
'''
num = []
sign = 0
if a[0] == '-':
sign = 1
for i in a[1:]:
num.append(int(i))
else:
for i in a:
num.append(int(i))
return list([sign,num])
該函數是將輸入的字符串列表化, 並且要考慮首位是否有負號。 該函數輸入是一個字符串, 輸出是一個二維列表, 其中第一維存儲了符號, 第二維存儲了無符號的整型列表
有了這兩個函數依舊不夠。。 這兩個都是功能函數, 我們還差一個接口函數,這個接口函數會調用這兩個功能函數完成運算並負責輸出。 首先確定該函數的輸入爲兩個字符串, 輸出爲結果字符串。 然後再對其中代碼進行設計如下:
def huge_add(self, a, b):
'''huge_add(string,string)
return string
'''
asign = self._huge_init(a)[0]
anum = self._huge_init(a)[1]
bsign = self._huge_init(b)[0]
bnum = self._huge_init(b)[1]
#switcher: 0 => +&+ 1=> -&+ 2=>+&- 3=>-&-
switcher = asign | bsign << 1
if switcher == 0:
result = ''.join(self._add_operate(anum, bnum))
if switcher == 1:
result = ''.join(self._sub_operate(bnum, anum))
if switcher == 2:
result = ''.join(self._sub_operate(anum, bnum))
if switcher == 3:
result = '-' + ''.join(self._add_operate(anum, bnum))
return result
該函數先是調用init函數對輸入列表化,然後根據符號位控制輸出。 其中調用到暫時還沒提到的_sub_operate, 因爲相異符號的加法就是一個減法運算, 同樣,減法運算也可以轉化爲相異符號的加法運算。 所以後面大家會發現, 減法的接口函數其實是依賴於加法接口函數的囧。。
但是必須強調, 減法和加法的算法是不同的。 而計算機的二進制算法是用補碼來表示被減數, 然後再做加法。 看似只用了一個算法就實現了加和減,但是求補也是一個算法嘛。。只是求補的算法遠比我的減法算法簡單效率高,呵呵
原本我也是想等到寫減法接口的時候再實現減法函數的,但是寫了才知道這個先後順序。。 要實現帶符號的加法運算就必須先把減法函數寫了T_T
下面是我的減法運算函數(不是減法接口函數):
#subtraction fun
def _sub_operate(self, anum, bnum):
carry = 0
rdigit = 0
rsign = 0
rnum = []
if len(bnum) > len(anum) or len(anum) == len(bnum) and \
anum < bnum:
tmp = anum
anum = bnum
bnum = tmp
rsign = 1
while anum:
adigit = 0
bdigit = 0
if anum:
adigit = anum.pop()
if bnum:
bdigit = bnum.pop()
rdigit = adigit - bdigit - carry
if rdigit < 0:
rdigit = rdigit + 10
carry = 1
else:
carry = 0
rnum.append(str(rdigit))
if rnum[-1] == '0' and len(rnum)-1:
rnum = rnum[0:-1]
if rsign:
rnum.append('-')
rnum.reverse()
result = rnum
return result
減法先對減數和被減數做了下大小比較, 把大數換到被減數位置去,這樣既可以減少逐位相減次數(減的次數是由減數位數決定的,所以減數越短減的次數越少)又可以將可能帶符號的減法換爲無符號減法運算(大數減小數必然爲正)。 而根據減法法則, 交換減數被減數只需要在結果填一個負號便可。 所以當發生交換時符號標誌(rsign)置1.
然後便是小學學的逐位相減,不足借位。 借位寄存器用完記得清零就好了。。
結果要除去高位的0,因爲高位0 是沒有意義的, 但需要特別處理結果爲0的狀況。 根據符號標誌設置一下符號便搞定咯。
接着比較了下二進制加法與我寫的十進制加法,發現兩個的計算方法是 一樣的,都是逐位累加,然後用進位寄存器來存儲進位。 看來加法確實是最簡單的啊
減法上二進制就厲害多了。因爲是用加補碼,溢出捨棄的方式做減法, 最高位做符號位時依然適用, 少了很多符號換來換去的麻煩。
補碼算法明顯比這個減法算法簡單, 按位取反再加一。 最後在做一次加法。 三個運算符就搞定咯。。
測試也順利通過了:
['2000', '1000', '1000'] Passed!
['0', '0', '0'] Passed!
['1', '0', '1'] Passed!
['0', '1', '-1'] Passed!
['337618', '3495', '334123'] Passed!
['-219', '-98', '-121'] Passed!
['3', '-32', '35'] Passed!
['-2085924839766513752338888384931203236916703635113918720651407820138886450957659038931612598272', '-1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136', '-1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136'] Passed!
['10429624198832568761694441924656016184583518.17556959360325703910069443225478829519465806299136', '1042962419883256876169444192465601618458351817556959360325703910069443225478828393565899456512', '1125899906842624'] Passed!
----------------------------------------------------------------------
Ran 1 test in 0.033s
OK
減法:
先寫測試用列:
#!/usr/bin/env python
from hugeoperation import HugeOperation
import unittest
class TestHugeOperation(unittest.TestCase):
def setUp(self):
self.testobj = HugeOperation()
self.testdata = {}
self.testdata['add'] = [
['2000', '1000', '1000'],
['0','0','0'],
['1', '0', '1'],
['0', '1', '-1'],
['337618', '3495', '334123'],
['-219', '-98', '-121'],
['3', '-32', '35'],
['-2085924839766513752338888384931203236916703635113918720651407820138886450957659038931612598272',\
'-1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136',\
'-1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136'],
['1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136',\
'1042962419883256876169444192465601618458351817556959360325703910069443225478828393565899456512',\
'1125899906842624' ]
]
self.testdata['sub'] = [['0', '1', '1'],
['1','1','0'],
['-1','0','1'],
['23','45','22'],
['13','5','-8'],
['26','-3','-29'],
['-2582249878086908587416174429825207663772263512260779234709013859305998087116852093665853482099976886055025678701140377600',\
'2239744742177804210557442280568444278121645497234649534899989100963791871180160945380877493271607115776',\
'2582249878086908589655919172003011874329705792829223512830659356540647622016841194629645353280137831435903171972747493376']
]
def test_add(self):
for i in self.testdata['add']:
self.assertEqual(i[0], self.testobj.huge_add(i[1], i[2]))
print i,
print ' Passed!\n'
def test_sub(self):
for i in self.testdata['sub']:
self.assertEqual(i[0], self.testobj.huge_sub(i[1], i[2]))
print i,
print ' Passed!\n'
if __name__=='__main__':
unittest.main()
加法函數,減法函數,初始化函數都有了,減法接口實現還難麼? 不多說了,大家請看:
#subtraction interface
def huge_sub(self, a, b):
if b[0] == '-':
b = b[1:]
else:
b = '-' + b
result = self.huge_add(a,b)
return result
對。。就是這麼Easy。。
測試通過:
['2000', '1000', '1000'] Passed!
['0', '0', '0'] Passed!
['1', '0', '1'] Passed!
['0', '1', '-1'] Passed!
['337618', '3495', '334123'] Passed!
['-219', '-98', '-121'] Passed!
['3', '-32', '35'] Passed!
['-2085924839766513752338888384931203236916703635113918720651407820138886450957659038931612598272', '-1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136', '-1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136'] Passed!
['1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136', '1042962419883256876169444192465601618458351817556959360325703910069443225478828393565899456512', '1125899906842624'] Passed!
['.0', '1', '1'] Passed!
['1', '1', '0'] Passed!
['-1', '0', '1'] Passed!
['23', '45', '22'] Passed!
['13', '5', '-8'] Passed!
['26', '-3', '-29'] Passed!
['-258224987808690858741617442982520766377226351226077923470901385930.5998087116852093665853482099976886055025678701140377600', '2239744742177804210557442280568444278121645497234649534899989100963791871180160945380877493271607115776', '2582249878086908589655919172003011874329705792829223512830659356540647622016841194629645353280137831435903171972747493376'] Passed!
----------------------------------------------------------------------
Ran 2 tests in 0.042s
OK
乘法:
寫算法之前先添加測試用列:
#!/usr/bin/env python
from hugeoperation import HugeOperation
import unittest
class TestHugeOperation(unittest.TestCase):
def setUp(self):
self.testobj = HugeOperation()
self.testdata = {}
self.testdata['add'] = [
['2000', '1000', '1000'],
['0','0','0'],
['1', '0', '1'],
['0', '1', '-1'],
['337618', '3495', '334123'],
['-219', '-98', '-121'],
['3', '-32', '35'],
['-2085924839766513752338888384931203236916703635113918720651407820138886450957659038931612598272',\
'-1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136',\
'-1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136'],
['1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136',\
'1042962419883256876169444192465601618458351817556959360325703910069443225478828393565899456512',\
'1125899906842624' ]
]
self.testdata['sub'] = [['0', '1', '1'],
['1','1','0'],
['-1','0','1'],
['23','45','22'],
['13','5','-8'],
['26','-3','-29'],
['-2582249878086908587416174429825207663772263512260779234709013859305998087116852093665853482099976886055025678701140377600',\
'2239744742177804210557442280568444278121645497234649534899989100963791871180160945380877493271607115776',\
'2582249878086908589655919172003011874329705792829223512830659356540647622016841194629645353280137831435903171972747493376']
]
self.testdata['mul'] = [
['12','3','4'],
['0','32','0'],
['-0','-32','0'],
['-54612','123','-444'],
['54612','-123','-444'],
['603822', '641', '942'],
['1831650372496', '2341234', '782344'],
['1007515750397959314', '1231231923', '818298918'],
['324518553658426726783156020576256','18014398509481984',\
'18014398509481984'],
]
def test_add(self):
for i in self.testdata['add']:
self.assertEqual(i[0], self.testobj.huge_add(i[1], i[2]))
print i,
print ' Passed!\n'
def test_sub(self):
for i in self.testdata['sub']:
self.assertEqual(i[0], self.testobj.huge_sub(i[1], i[2]))
print i,
print ' Passed!\n'
def test_multiple(self):
for i in self.testdata['mul']:
self.assertEqual(i[0], self.testobj.huge_multip(i[1], i[2]))
print i,
print ' Passed!\n'
if __name__=='__main__':
unittest.main()
然後開始設計算法:
小學學過做乘法是逐位相乘,然後累加。 最後的累加是個陣列式。 累加我們可以調用之前設計的無符號加法函數來完成。 所以在計算機上乘法器是基於加法器一說便是這麼由來的。 先來看代碼:
#multiplication fun
def _multi_poperate(self, anum, bnum):
carry = 0
rnum = []
strrnum = []
accnum = []
acclist = []
#Switch the short num to multiplicator
if len(anum) < len(bnum):
tmpnum = anum
anum = bnum
bnum = tmpnum
#逐位相乘,乘積的陣列式放在二維列表acclist中,
#acclist的下標整好可以標識該行數據的進位。
while bnum:
adigit = 0
bdigit = 0
bdigit=bnum.pop()
for adigit in anum[::-1]:
product = (bdigit * adigit + carry) % 10
carry = (bdigit * adigit + carry)/10
accnum.append(product)
if carry:
accnum.append(carry)
carry = 0
acclist.append(accnum[::-1])
accnum = []
t = 1;
rnum = acclist[0];
#累加運算, 注意由於輸出是字符型列表,要實現累加需要對每次輸出進行整型轉型
while t < len(acclist):
for i in range(0,t):
acclist[t].append(0)
b = acclist[t]
accnum = self._add_operate(rnum,b)
for i in accnum:
rnum.append(int(i))
t +=1
#去除高位多餘的0
zerosign = 0
for i in rnum:
if i:
zerosign = 1
if zerosign:
strrnum.append(str(i))
if not strrnum:
strrnum = ['0']
result = strrnum
return result
PS:中文註釋是寫文章的時候才加的。。之前覺得邏輯很簡單,沒有必要
首先做的依然是根據位數交換乘數被乘數位置,減少陣列式累加次數。 之後是逐位相乘後放入陣列式中. 注意算乘法時最後有進位的話記得把進位進上去,並把進位寄存器清零。 每次乘出的積會倒序,所以需要對列表正序列化。
然後對陣列做累加,每次取陣列式裏面的數時要根據下標補零 (十進制的移位??)
最後去除高位多餘的0, 完成。
與二進制相比較算法大抵相當,都是做陣列式累加。 只是二進制乘法的逐位相乘就是一個左移位操作,非常便捷(因爲乘數要麼爲0,要麼爲1.。 爲0時該行直接添零,爲一時根據該位位置直接移位就好了) 。 串行乘法器所做的也就是‘移位-累加’操作罷了。 後來出現的並行乘法器算法也是一樣的, 不過藉由全加器實現了接受3個輸入(之前該位的累加結果+陣列值+進位值 ) 和提供兩個輸出(進位值+累加結果) 。 這樣便可以將執行效率提高N倍(理論值, N 爲被乘數位寬)。 但是歸根而言依舊是逐位相乘,再陣列累加的方法,只是將累加操作併發了而已
最後是乘法的接口函數:
符號判斷很簡單,符號位求異或。。 計算機做乘法時也是這麼幹的。。
def huge_multip(self, a, b):
asign = self._huge_init(a)[0]
anum = self._huge_init(a)[1]
bsign = self._huge_init(b)[0]
bnum = self._huge_init(b)[1]
#rsign: 0 -> + ; 1 -> -
rsign = asign ^ bsign
result = ''.join(self._multi_poperate(anum, bnum))
if rsign:
result = '-' + result
return result
測試結果不發了。。 等除法做完一起貼上來。。
除法:
除法這個纔是最有意思的。 二進制做除法相當爽啊, 直接做‘差-》移位’就搞定了。 因爲商只可能是0和1。 按照除法的法則,我們也可以從高位開始做差,然後移位。 但是我在這裏使用了另一種方法實現了類似操作:
def _div_operate(self, anum, bnum):
trynum = [1]
result = []
n = 0
# if divider is bigger than dividend, return '0'
if len(anum) < len(bnum) or len(anum) == len(bnum) and \
anum[0] < bnum[0]:
return ['0']
else:
#Use the diff of tow number's length to ensure the try number's bit
bitdiff = len(anum) - len(bnum)
#If the first number of divider is bigger than dividend, try number's bit -1
if anum[0] < bnum[0]:
n = 1
#Initial try number as a '10000' like number
for i in range(n, bitdiff):
trynum.append(0)
#Use try number to multiply the dividend and compare with divider
for i in range(0,len(trynum)):
k = 1
#If result number is bigger than divider,
#break out and set the littler bit to 1
#If result is at the last bit, do not set the littler bit and break out.
while 1:
if k == 10:
if i < len(trynum)-1:
trynum[i] = 9
break
else:
break
strtryresult = self.huge_multip(bnum, trynum)
tryresult = []
for n in strtryresult:
tryresult.append(int(n))
if len(tryresult) > len(anum) or len(tryresult) == len(anum) and \
tryresult > anum:
if i < len(trynum) - 1:
trynum[i] -= 1
trynum[i+1] = 1
break
else:
trynum[i] -= 1
break
else:
trynum[i] += 1
k +=1
for n in trynum:
result.append(str(n))
return result
利用try number 逐位嘗試,直到找到小於,但最接近商的整數爲止(如果能整除則剛好相等了)。 先通過列表長度確定try number的位數,然後從高位開始遞增並與被除數相乘, 當大於除數時回退1, 並將挨着的低位置爲1, 直到最低位即得出結果了。
除法接口函數:
def huge_div(self, a, b):
asign = self._huge_init(a)[0]
anum = self._huge_init(a)[1]
bsign = self._huge_init(b)[0]
bnum = self._huge_init(b)[1]
#rsign: 0 -> + ; 1 -> -
if bnum[0] == 0 and len(bnum) == 1:
return None
else:
rsign = asign ^ bsign
result =''.join(self._div_operate(anum, bnum))
if rsign:
result = '-' + result
return result
和乘法一樣, 用符號位異或來確定符號。
測試結果:
['2000', '1000', '1000'] Passed!
['0', '0', '0'] Passed!
['1', '0', '1'] Passed!
['0', '1', '-1'] Passed!
['337618', '3495', '334123'] Passed!
['-219', '-98', '-121'] Passed!
['3', '-32', '35'] Passed!
['-2085924839766513752338888384931203236916703635113918720651407820138886450957659038931612598272', '-1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136', '-1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136'] Passed!
['1042962419883256876169444192465601618458351817556959360325703910069443225478829519465806299136', '1042962419883256876169444192465601618458351817556959360325703910069443225478828393565899456512', '1125899906842624'] Passed!
.['3', '6', '2'] Passed!
[None, '2', '0'] Passed!
['0', '0', '3'] Passed!
['1', '3', '2'] Passed!
['25', '300', '12'] Passed!
['-3', '-6', '2'] Passed!
['-3', '6', '-2'] Passed!
['3', '-6', '-2'] Passed!
['2', '115792089237316195423570985008687907853269984665640564039457584007913129639936', '57896044618658097711785492504343953926634992332820282019728792003956564819968'] Passed!
['57896044618658097711785492504343953926634992332820282019728792003956564819968', '115792089237316195423570985008687907853269984665640564039457584007913129639936', '2'] Passed!
.['12', '3', '4'] Passed!
['0', '32', '0'] Passed!
['-0', '-32', '0'] Passed!
['-54612', '123', '-444'] Passed!
['54612', '-123', '-444'] Passed!
['603822', '641', '942'] Passed!
['1831650372496', '2341234', '782344'] Passed!
['1007515750397959314', '1231231923', '818298918'] Passed!
['324518553658426726783156020576256', '18014398509481984', '18014398509481984'] Passed!
.['0', '1', '1'] Passed!
['1', '1', '0'] Passed!
['-1', '0', '1'] Passed!
['23', '45', '22'] Passed!
['13', '5', '-8'] Passed!
['26', '-3', '-29'] Passed!
['-2582249878086908587416174429825207663772263512260779234709013859305998087116852093665853482099976886055025678701140377600', '2239744742177804210557442280568444278121645497234649534899989100963791871180160945380877493271607115776', '2582249878086908589655919172003011874329705792829223512830659356540647622016841194629645353280137831435903171972747493376'] Passed!
.
----------------------------------------------------------------------
Ran 4 tests in 0.698s
OK
若不考慮call 等的損耗的話,算法上比二進制時間複雜度多一倍左右(因爲要退位)。