我有一个python函数来生成斐波那契数字:
def fib(n):
return ((1+math.sqrt(5))**n - (1-math.sqrt(5))**n)/(2**n*math.sqrt(5))
我可以提供最多700的fib功能数字,它开始
OverflowError: (34, 'Numerical result out of range')
我是否需要使用特殊类型来解决这个问题?
答案 0 :(得分:7)
问题是你正在使用双打来计算价值而双打正在溢出。 双打只给出了大约第85个斐波纳契数的精确解。
如果您想要快速准确的计算,最好使用基于更好的递归关系的算法,并使用python bignum整数。
特别是你可以使用:
fib(2*n) = fib(n)^2 + fib(n-1)^2
fib(2*n-1) = fib(n)*(2*fib(n-1)+fib(n))
或等效矩阵求幂公式(原谅丑陋的格式化)
[ F_n F_{n-1} ] [ 1 1 ] ^N
[ ] = [ ]
[ F_{n-1} F_{n-2} ] [ 1 0 ]
这两项都会产生需要O(log(N))
计算而不是O(N)
的算法。
这是complete solution in pseudo-code
如果您确实想使用双精度和显式公式执行计算,那么可以调整公式以提供更快的速度,直到大约第1500个斐波纳契数时才会溢出,并保持与您的版本相同的精度。 IIRC是:
def fib(n):
return round( ((1+math.sqrt(5))/2)**n / math.sqrt(5) )
答案 1 :(得分:4)
很容易隔离错误
>>> (1+math.sqrt(5))**700
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OverflowError: (34, 'Numerical result out of range')
此方法无效,因为浮点数没有足够的精度
例如,这里
>>> (1+math.sqrt(5))**600
1.024664165563927e+306
您只使用前15个左右的数字。当你做任何算术
时,剩余的291将被视为零 的详情,请参阅accuracy problems with floating point numbers答案 2 :(得分:4)
您可以随时尝试这种方法:
def fib(n, memo={0:0, 1:1}):
if n not in memo:
memo[n] = fib(n-1) + fib(n-2)
return memo[n]
print fib(800)
输出:
69283081864224717136290077681328518273399124385204820718966040597691435587278383112277161967532530675374170857404743017623467220361778016172106855838975759985190398725
答案 3 :(得分:2)
如果您确实想要使用该算法,并且希望超出内置float
的限制,那么是的,您需要一个不同的类型。
如果您只想得到一个近似答案而不是例外,那很容易;你可以开箱即用。但是,如果您还想消除舍入误差,则无法获得无限精度(这需要无限的时间/空间),因此您必须知道如何计算输入范围所需的精度。 (我将把它作为读者的练习。)
标准库类型decimal.Decimal
可能就是您所需要的。它根据IEEE-854标准提供任意精度的固定或浮点十进制算术。有很多情况下它不可用,因为它没有提供足够的数学函数,但你只需要基本的算术和sqrt
,这很好。它对于大数字来说也可能很慢,但如果你只想在几个三位数上计算fib
,那就足够了。
当Decimal
不足时,有许多第三方模块,通常包含行业标准C库,如gmp / mpfr,例如bigfloat。
以下是如何获得无限范围,但舍入误差与内置浮点大致相同:
>>> s5 = decimal.Decimal(5).sqrt()
>>> def fib(n):
... return ((1+s5)**n - (1-s5)**n)/(2**n*s5)
>>> fib(800)
Decimal('6.928308186422471713629008226E+166')
>>> int(fib(800))
69283081864224717136290082260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000L
>>> s5 = bigfloat.sqrt(5)
>>> def fib(n):
... return ((1+s5)**n - (1-s5)**n)/(2**n*s5)
>>> fib(800)
BigFloat.exact('6.9283081864226567e+166', precision=53)
>>> int(fib(800))
69283081864226566841137772774650010139572747244991592044952506898599601083170460360533811597710072779197410943266632999194601974766803264653830633103719677469311107072L
但请注意,如果你完美地完成数学计算,这些都不是你得到的答案。你有四舍五入的错误输了24位数。 (值不同的原因是bigfloat
在基数为2的四舍五入,基数为10的decimal
。)
要解决这个问题,您需要更高的精确度。所有库都提供了一些改变精度的方法; bigfloat
有比大多数更方便的选项,但没有一个太繁重:
>>> decimal.getcontext().prec = 300
>>> s5 = decimal.Decimal(5).sqrt()
>>> def fib(n):
... return ((1+s5)**n - (1-s5)**n)/(2**n*s5)
>>> fib(800)
69283081864224717136290077681328518273399124385204820718966040597691435587278383112277161967532530675374170857404743017623467220361778016172106855838975759985190398725.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000048
>>> def fibp(n, p):
... with bigfloat.precision(p):
... s5 = bigfloat.sqrt(5)
... return ((1+s5)**n - (1-s5)**n)/(2**n*s5)
>>> fibp(800, 125)
BigFloat.exact('6.92830818642247171362900776814484912138e+166', precision=125)
>>> int(fibp(800, 125))
69283081864224717136290077681448491213794574774712670552070914552025662674717073354503451578576268674564384721027806323979200718479461097490537109958812524476157132800L