Fast Fibonacci calculation

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 # square it
            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():
    # elments (0,0), (0,1), (1,1) of a symmetric 2x2 matrix are a, b, c.
    # b is also the (1,0) element because the matrix is symmetric

    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

    def __imul__(self, other):
        # this multiplication does work correctly because we 
        # are multiplying powers of the same symmetric matrix
        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 # square it
            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).

+4
4

, . , O (logn) , . , .

fib

, "" . O (log (n)) :

fib2

from functools import lru_cache

@lru_cache(None)
def fib(n):
    if n in (0, 1):
        return 1
    if n & 1:  # if n is odd, it faster than checking with modulo
        return fib((n+1)//2 - 1) * (2*fib((n+1)//2) - fib((n+1)//2 - 1))
    a, b = fib(n//2 - 1), fib(n//2)
    return a**2 + b**2

note . , F (2N) F (2N-1) F (N) F (N-1). , - , , . Python - memoization , .

+5

-, k- . , $\ sqrt (5) $ . , .

def rootiply(a1,b1,a2,b2,c):
    ''' multipy a1+b1*sqrt(c) and a2+b2*sqrt(c)... return a,b'''
    return a1*a2 + b1*b2*c, a1*b2 + a2*b1

def rootipower(a,b,c,n):
    ''' raise a + b * sqrt(c) to the nth power... returns the new a,b and c of the result in the same format'''
    ar,br = 1,0
    while n != 0:
        if n%2:
            ar,br = rootiply(ar,br,a,b,c)
        a,b = rootiply(a,b,a,b,c)
        n /= 2
    return ar,br

def fib(k):
    ''' the kth fibonacci number'''
    a1,b1 = rootipower(1,1,5,k)
    a2,b2 = rootipower(1,-1,5,k)
    a = a1-a2
    b = b1-b2
    a,b = rootiply(0,1,a,b,5)
    # b should be 0!
    assert b == 0
    return a/2**k/5

if __name__ == "__main__":
    assert rootipower(1,2,3,3) == (37,30) # 1+2sqrt(3) **3 => 13 + 4sqrt(3) => 39 + 30sqrt(3)
    assert fib(10)==55
+1

Wikipedia,

n ≥ 0 Fn ​​phi ^ n/sqrt (5), phi - . ,

0

, .

Aaron , , . , , , . , :

formula

Φ O(M(n)), M(n) - ( linearithmic), n - .

, log (O(M(n)•log(n)), (O(M(n))), exp (O(M(n)•log(n)).

(O(M(n))), (O(M(n))) (O(n)).

- O(n•log^2(n)•log(log(n))) n .


I did not thoroughly analyze the division algorithm, but if I read it correctly, each bit may need recursion (you need to divide the number of log(2^n)=ntimes), and each recursion needs to be multiplied. Therefore, it cannot be better than O(M(n)•n), and exponentially worse.

0
source

Source: https://habr.com/ru/post/1648291/


All Articles