Firstly, here’s a basic implementation of Fibonacci using recursion:
using Numbers = std::vector<unsigned long long>;
Numbers cache( 5000, 0 );
unsigned long long fibonacciRecursiveImpl( size_t n, Numbers& cache )
{
if ( n == 0 ) return 0ULL;
if ( n == 1 ) return 1ULL;
if ( cache[n] != 0 ) return cache[n];
auto num = fibonacciRecursiveImpl( n-1, cache ) + fibonacciRecursiveImpl( n-2, cache );
cache[n] = num;
return num;
}
unsigned long long fibonacciRecursive( size_t n )
{
return fibonacciRecursiveImpl( n, cache );
}
This works fine for small numbers (e.g. up to 20). I was interested to know where it would fail first, data overflow (due to the size of the numbers involved) or stack overflow (due to the recursive approach)? I implemented this on a MacBook using Apple LLVM 7.0 and the C++14 flag, but no other special switches set. It turns out that the overflow problems kick in for n > 93, but there was no sign of stack overflows, even up for n ~ 2000.
Even if there was a stack overflow, you could still use recursion, but change to a tail recursive solution (many C++ compilers will eliminate tail calls, essentially turning this into an iterative solution):
// Implement recursive approach with possibility for "Tail call elimination",
// avoids any concerns about stack overflow
unsigned long long fibonacciTailRecursiveImpl( size_t n, unsigned long long prevprev, unsigned long long prev )
{
if ( n == 0 ) return prevprev;
if ( n == 1 ) return prev;
return fibonacciTailRecursiveImpl( n-1, prev, prevprev + prev );
}
unsigned long long fibonacciTailRecursive( size_t n )
{
return fibonacciTailRecursiveImpl( n, 0, 1 );
}
So how to avoid the data overflow for Fibonacci over n = 93? At that point, you need to introduce a large number type with its own internal representation of large integers and suitable operator+ implementation. I used one such, BigNum, for this HackerRank challenge. The implementation stores the large integer as a string, with each character taking values in the range [0,100]. This reduces the number of digit-by-digit additions by a factor of 10 compared to a straight decimal representation.
I replaced unsigned long long with BigNum in the recursive solution above and verified that it returns the correct answers for n ~ 2000, with no stack overflows. Here, I’ll show it in an iterative solution (if you don’t want to keep a cache around, this is highly memory efficient, because you only need to store the previous two values):
BigNum fibonacciBigNumIterativeImpl( size_t n )
{
BigNum prevprev(0);
BigNum prev(1);
if ( n == 0 ) return prevprev;
if ( n == 1 ) return prev;
BigNum num(0);
for ( size_t i = 2; i <= n; ++i )
{
num = prevprev + prev;
prevprev = prev;
prev = num;
}
return num;
}
std::string fibonacciBigNumIterative( size_t n )
{
auto result = fibonacciBigNumIterativeImpl( n );
return result.toString();
}