6

I am trying to make a program to find the nth Fibonacci number for 1 < n < 10^19.

Here is my code using dynamic programming.

memo = {}
def fib(n):
    if n in memo:
        return memo[n]
    if n <= 2:
        f = 1
    else:
        f = fib(n-1) + fib(n-2)
    memo[n]=f
    return f
print fib(input()) % 1000000007

My code does not seem to work for large numbers. I get invalid response error. Any suggestions?

Mehdi Alali
  • 163
  • 1
  • 11

4 Answers4

11

Getting the Nth fibonacci number when N is 10^19 is not goign to work if you do it the naive way (at least i would guess it won't work).

There's a much better way to do it. And this technique works with lots of series' like this. It's called the Fibonacci Q Matrix.

enter image description here

Where

enter image description here

Think of it like this:

You have some matrix which transforms vector A into B:

enter image description here

Filling in those entries is easy. The special part is that this is now a matrix operator, and so if we want the 1000th Fibonacci number, we just need to do matrix multiplication.

You could do this with a loop, but it's going to take you quite a while to get all the way up to 10^19, and doing 10^19 matrix multiplications (even when they're small) is going to take a fair while too.

Instead, we take another shortcut. x^N can be rewritten as the product of power where they sum to N, i.e.

x**100 == x**90 * x**10

So the aim is to get large numbers in the indices without doing lots of calculations:

x**2 is just as difficult as x*x - they take the same amount of time. But x*x*x*x gives the same answer as (x**2)**2 while requiring an extra multiplication. The gains get more as you go to higher powers. So if you break down the exponent into powers of 2 (any power works, but this is the simplest case),

X**100 == X**64 * X**32 * X**4

i.e.

X**100 == (((((X**2)**2)**2)**2)**2)**2 + ...

So what you do, is work out the powers of two of the total power you want to reach, and then take the product of those powers of two of the Q matrix.

This seems to work for me:

fib_matrix = [[1,1],
              [1,0]]

def matrix_square(A, mod):
    return mat_mult(A,A,mod)


def mat_mult(A,B, mod):
  if mod is not None:
    return [[(A[0][0]*B[0][0] + A[0][1]*B[1][0])%mod, (A[0][0]*B[0][1] + A[0][1]*B[1][1])%mod],
            [(A[1][0]*B[0][0] + A[1][1]*B[1][0])%mod, (A[1][0]*B[0][1] + A[1][1]*B[1][1])%mod]]


def matrix_pow(M, power, mod):
    #Special definition for power=0:
    if power <= 0:
      return M

    powers =  list(reversed([True if i=="1" else False for i in bin(power)[2:]])) #Order is 1,2,4,8,16,...

    matrices = [None for _ in powers]
    matrices[0] = M

    for i in range(1,len(powers)):
        matrices[i] = matrix_square(matrices[i-1], mod)


    result = None

    for matrix, power in zip(matrices, powers):
        if power:
            if result is None:
                result = matrix
            else:
                result = mat_mult(result, matrix, mod)

    return result

print matrix_pow(fib_matrix, 10**19, 1000000007)[0][1]

And then, you can take it a step even further - it's just a 2x2 matrix, so we can diagonalise it, and then get the formula for the nth fibonacci number, just as a function of n - with no recursion. Like this:

As above, we compute the matrix that takes us from one step to the next:

enter image description here

And then the relationship to get from one set of numbers to the next:

enter image description here

where we can chain these matrix multiplications:

enter image description here

Where there's nothing to stop us going back all the way to the first fibonacci numbers:

enter image description here

now the game becomes "how do we raise that matrix to the power n" - which is exactly what's done in the code above. But there is a better way than the solution i pose above. We can decompose the Q-matrix into eigen values and vectors, a write it like so:

enter image description here

Where U is a unitary matricies that contain the eigen values of Q, and Λ is the matrix of corersponding eigenvalues. These eigenvalues and vecors are:

enter image description here

enter image description here

And then you use one of the standard advantages of this style of decomposition, where when you raise it to a power, the adjacent U matrix and it's inverse combine to give the unitary matrix, leaving you with a single U and it's inverse at the ends, with a chain of diagonal matrices in the middle, where raising these to a power is trivial:

enter image description here enter image description here enter image description here

So now we have all we need to write the nth Fibonacci number in terms of just a single formula, no recursion. I'll complete it tomorrow/some time later this week though...

will
  • 10,260
  • 6
  • 46
  • 69
  • If you're actually doing this seriously, then you should diagonalize the matrix - then you can just raise it to arbitrary powers easily. – will Sep 01 '16 at 16:58
  • Hey @will, this helped with a fibonacci sequence a lot. But, a little bit off-topic, but I hope you can help - I've got an integer sequence with custom defined formula for 2n and 2n + 1 items. Do you know if I can approach the problem in a similar to fibonacci sequence way and make a similar Q-matrix for a custom sequence? Thanks! – alecxe Sep 01 '16 at 17:10
  • 1
    What is the recursion relation? If the offset is fixed, (i.e. it is a [constant recursive sequence](https://en.wikipedia.org/wiki/Constant-recursive_sequence)) then you can always construct this matrix (it just varies in size). If it is relative (i.e. 4th is a function of 4/2 = 2nd and 4/2+1 = 3rd, 20th is a function of 10th and 11th, etc) then you cannot - but there are still ways to get the solution more easily - post a question. – will Sep 02 '16 at 09:09
  • FYI, for any reading this, of you go down the diagonalisation route, then you can just strip out an analytic, non recursive formula for the nth fibonacci number. – will Dec 21 '17 at 20:37
9

At O(n) efficiency you'll never get there. Not specifically code-related, but Dijkstra's note "In honor of Fibonacci" describes a way to find F(n) in O(log(n)) efficiency.

F(2n-1) = F(n-1)^2 + F(n)^2

F(2n) = (2*F(n-1)+F(n))*F(n)

That you could not only do, but still do recursively.

Community
  • 1
  • 1
  • 1
    +1, though this formula is still hopeless for computing `F(n)` directly for `n` up to `10^19`. (No formula will work here: the result is simply too large to be storable.) Combined with reduction modulo `1000000007`, though, this would work. – Mark Dickinson Feb 16 '15 at 20:01
  • @Mark Dickinson: At log(n) complexity, I think this formula gets there in 50 or so iterations, no? Too many subsidiary values to calculate? –  Feb 16 '15 at 20:07
  • @JohnPirie: I think he's just referring to the fact that Fib(10^19) ~ 2.2041233236015342e+2089876402499787337, and so unless we're reducing we're hosed. :-) – DSM Feb 16 '15 at 20:10
  • @DSM: ah, so a simple estimate would be just as effective; thank you –  Feb 16 '15 at 20:16
  • @JohnPirie: Yes, what DSM said. The OP doesn't say so directly, but it looks as though what (s)he actually wants is the reduction of `F(n)` modulo `1000000007` rather than `F(n)` itself. (Sounds like a typical Project-Euler-style challenge problem rather than a real-world computation.) – Mark Dickinson Feb 16 '15 at 20:20
2

Python has a default recursion limit of 1000 (usually). To find out what the exact limit is on your system:

>>> import sys
>>> sys.getrecursionlimit()

Firstly, if you want to write this recursively and you're using Python 3.2 and above (which it doesn't look like you are, judging from the print statement) then you can use @functools.lru_cache(maxsize=128, typed=False) like so:

import functools

@functools.lru_cache()
def fib(n):
    if n <= 2:
        return 1
    else:
        return fib(n-1) + fib(n-2)

Having said that, this still won't be very fast for large numbers. The better way to do this is to write an iterative solution and all you need to "memoize", at any given time, is the last 2 numbers.

You can of course use the matrix form for even better performance.

Ultimately, for n being as large as 10**19 you're going to have a hard time writing anything that runs in Python without giving you an OverflowError.

s16h
  • 4,647
  • 1
  • 21
  • 33
  • 2
    The OP didn't describe it very well, but I'm pretty sure that the OP's `% 1000000007` is hinting at the fact we only need to get the answer mod 1000000007. The matrix form (or the reduction formula, as you prefer) is probably going to be necessary anyway, because there's no way you can do ~10^19 iterations for the upper limit. – DSM Feb 16 '15 at 19:43
  • @DSM the way you do it is by not doign the iterations in the first place. There is a much more efficient way to calculate Fibonacci numbers. – will Feb 16 '15 at 19:57
  • @will: I'm not sure what you mean, given that I just said the iterations are impossible. Using matrix multiplication or the equivalent reduction formula (as I just did -- which I see John Pirie just posted), I can get the right answer in about 190 ns. – DSM Feb 16 '15 at 19:58
  • @DSM I was just typing up an answer with something like this in it :-/ – will Feb 16 '15 at 20:00
  • @DSM i didn't read what you wrote properly. I agree with you. – will Feb 16 '15 at 20:03
  • @will: I get the same. – Mark Dickinson Feb 16 '15 at 20:38
  • cool. The code i have included below has no problems going up to 10^119. Haven't tried higher, but don't see it being a problem. – will Feb 16 '15 at 20:47
  • @will: Well, a little number theory shows that the sequence of Fibonacci numbers modulo `1000000007` is periodic with a period of `2000000016`, so it's not a terribly interesting problem for larger `n`: just reduce `n` modulo `2000000016` first. – Mark Dickinson Feb 16 '15 at 22:49
  • Yah i saw the pisano period page while reading around. Haven't read into it any deeper though. Doing the modulo on the starting `n` would be faster though. – will Feb 16 '15 at 22:54
2

I do not think you can go up to 1E19 with this, but here is how you avoid the double overflow and the recursion depth limit:

import decimal
import operator


def decimal_range(start, stop, step=1):
    """Provides an alternative to `xrange` for very high numbers."""
    proceed = operator.lt
    while proceed(start, stop):
        yield start
        start += step


def fib(n):
    """
    Computes Fibonacci numbers using decimal.Decimal for high 
    precision and without recursion
    """
    a, b = decimal.Decimal(0), decimal.Decimal(1)
    for i in decimal_range(0, n):
        a, b = b, a + b
    return a

On my machine, it took 26.5 s to compute 1E6, but I can't guarantee the correctness of the result:

In [26]: %time f2(n)
CPU times: user 26.4 s, sys: 130 ms, total: 26.5 s
Wall time: 26.5 s
Out[26]: Decimal('1.953282128707757731632014830E+208987')

The iterator is taken from this SO thread with minimal alterations, while the fib function can be found in this other thread.

Community
  • 1
  • 1
logc
  • 3,813
  • 1
  • 18
  • 29