I saw a comment on Google+ a few weeks ago in which someone demonstrated a direct calculation of Fibonacci numbers that was not based on recursion and did not use memoization. He actually just remembered the last 2 digits and continued to add them. This is an O (n) algorithm, but it implemented it very cleanly. Therefore, I quickly pointed out that a faster way is to use the fact that they can be calculated as the power of the matrix [[0,1], [1,1]], and this requires only O (log (N) ), calculation.
The problem, of course, is that this is far from an optimal past at a certain point. It is effective if the numbers are not too large, but they grow in length at the rate of N * log (phi) / log (10), where N is the Nth Fibonacci number and phi is the golden ratio ((1 + sqrt (5) ) / 2 ~ 1.6). As it turned out, log (phi) / log (10) is very close to 1/5. Thus, the number of the Nth Fibonacci can have approximately N / 5 digits.
Matrix multiplication, heck even number multiplication, becomes very slow when numbers start to have millions or billions of digits. So F (100,000) took about 0.03 seconds to compute (in Python), while F (1,000,000) took about 5 seconds. This is almost O (log (N)) growth. My assessment was that this method, without improvements, only optimizes the calculation as O ((log (N)) ^ (2.5)) or so.
Calculating the billionth Fibonacci number at such a rate would be excessively slow (although it would only have 1,000,000,000/5 digits to fit easily into 32-bit memory).
Does anyone know of an implementation or an algorithm that will allow faster computation? Perhaps something that will allow you to calculate the trillionth Fibonacci number.
And to be clear, I'm not looking for an approximation. I'm looking for an exact calculation (to the last digit).
Edit 1: I am adding Python code to show what I consider the O ((log N) ^ 2.5) algorithm.
from operator import mul as mul
from time import clock
class TwoByTwoMatrix:
__slots__ = "rows"
def __init__(self, m):
self.rows = m
def __imul__(self, other):
self.rows = [[sum(map(mul, my_row, oth_col)) for oth_col in zip(*other.rows)] for my_row in self.rows]
return self
def intpow(self, i):
i = int(i)
result = TwoByTwoMatrix([[long(1),long(0)],[long(0),long(1)]])
if i <= 0:
return result
k = 0
while i % 2 == 0:
k +=1
i >>= 1
multiplier = TwoByTwoMatrix(self.rows)
while i > 0:
if i & 1:
result *= multiplier
multiplier *= multiplier
i >>= 1
for j in xrange(k):
result *= result
return result
m = TwoByTwoMatrix([[0,1],[1,1]])
t1 = clock()
print len(str(m.intpow(100000).rows[1][1]))
t2 = clock()
print t2 - t1
t1 = clock()
print len(str(m.intpow(1000000).rows[1][1]))
t2 = clock()
print t2 - t1
Edit 2:
It seems that I did not take into account the fact that it len(str(...))will make a significant contribution to the overall test execution time. Change tests to
from math import log as log
t1 = clock()
print log(m.intpow(100000).rows[1][1])/log(10)
t2 = clock()
print t2 - t1
t1 = clock()
print log(m.intpow(1000000).rows[1][1])/log(10)
t2 = clock()
print t2 - t1
reduced operating time to 0.008 seconds and 0.31 seconds (from 0.3 seconds and 5 seconds when they were used len(str(...))).
M = [[0,1], [1,1]], N, [[F (N-2), F (N-1)], [F (N-1), F (N)]],
(0,1) (1,0) , . ( Python3, Python2.7 ):
class SymTwoByTwoMatrix():
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def __imul__(self, other):
self.a, self.b, self.c = \
self.a * other.a + self.b * other.b, \
self.a * other.b + self.b * other.c, \
self.b * other.b + self.c * other.c
return self
def intpow(self, i):
i = int(i)
result = SymTwoByTwoMatrix(1, 0, 1)
if i <= 0:
return result
k = 0
while i % 2 == 0:
k +=1
i >>= 1
multiplier = SymTwoByTwoMatrix(self.a, self.b, self.c)
while i > 0:
if i & 1:
result *= multiplier
multiplier *= multiplier
i >>= 1
for j in range(k):
result *= result
return result
F (100 000) 0,006, F (1,000,000) 0,235 F (10 000 000) 9,51 .
. 45% , ,
phi/(1 + 2 * phi + phi * phi) ~ 23,6%.
(0,0) M ^ N N- :
for i in range(15):
x = m.intpow(i)
print([x.a,x.b,x.c])
[1, 0, 1]
[0, 1, 1]
[1, 1, 2]
[1, 2, 3]
[2, 3, 5]
[3, 5, 8]
[5, 8, 13]
[8, 13, 21]
[13, 21, 34]
[21, 34, 55]
[34, 55, 89]
[55, 89, 144]
[89, 144, 233]
[144, 233, 377]
[233, 377, 610]
, (0,0) 1/(1 + phi + phi * phi) ~ 19%. lru_cache F (2N) F (2N-1) , 4 (.. 75%). , , , 1 N . , , N. lru_cache, " " , , .
SymTwoByTwoMatrix, lru_cache-of-F (2N) - -F (2N-1) 40 , , N 10 . , , , - Python int. , . -O (N) , ( ) F (N) Theta(n).