序
这两天心血来潮, 想熟悉下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 等的损耗的话,算法上比二进制时间复杂度多一倍左右(因为要退位)。