I am trying to solve a bigger problem, and I think that an important part of the program is spent on inefficient computations.
I need to compute for a given number N, th
The Fibonacci numbers are given by Binet's formula
F(n) = ( phi^n - (1-phi)^n ) / \sqrt{5}
where phi
is the golden ratio,
phi = (1 + \sqrt{5}) / 2.
This can be implemented straightforwardly (Python example):
<<fibonacci_binet.py>>=
phi = (1 + 5**0.5) / 2
def fib(n):
return int(round((phi**n - (1-phi)**n) / 5**0.5))
Because of floating-point rounding errors, this will however only give the right result for n < 70
.
Binet's formula can be inverted by ignoring the (1-phi)^n
term, which disappears for large n
. We can therefore define the inverse Fibonacci function that, when given F(n)
, returns n
(ignoring that F(1) = F(2)
):
<<fibonacci_binet.py>>=
from math import log
def fibinv(f):
if f < 2:
return f
return int(round(log(f * 5**0.5) / log(phi)))
Here rounding is used to our advantage: it removes the error introduced by our modification to Binet's formula. The function will in fact return the right answer when given any Fibonacci number that can be stored as an exact integer in the computer's memory. On the other hand, it does not verify that the given number actually is a Fibonacci number; inputting a large Fibonacci number or any number close to it will give the same result. Therefore you can use this idea to find the Fibonacci number closest to a given number.
The idea, then is to apply the inverse Fibonacci map to find N
and M
, the two closest Fibonacci numbers on either side, then use the direct Fibonacci map to compute P = F(N)
and Q = F(M)
. This involves more computation, but less searching.
On Scala find the closet Fibonaci number looks also very simple:
val phi = (1 + sqrt(5))/2
def fib(n: Long): Long = round( ( pow(phi,n) - pow((1-phi),n) ) / sqrt(5) )
def fibinv(f: Long): Long = if (f < 2) f else round( log(f * sqrt(5) ) /log(phi))
Build a table of Fibonacci numbers that will fit in 8 bytes; there's only 94. That will save you calculating them through each iteration. There's no need for floating point math here.
Then use a binary search to find the number below and above your number in the time. That will save you comparing all the numbers, and reduce your search to a constant search time.
This meets your requirements, but note that your requirements do not specify what should be returned for N such that there is no Q in 64 bit integer space, i.e. N > 12,200,160,415,121,876,738. If you care about that, decide how you want to handle it. :)
#include "stdint.h"
#include "stdio.h"
#include "stdlib.h"
#include "time.h"
/* build a table of all fibonacci numbers that fit in a uint64_t. */
static const int fibonacciCount = 94;
uint64_t fibonacciSequence[fibonacciCount];
static void precalc(void) {
fibonacciSequence[0] = 0;
fibonacciSequence[1] = 1;
for (int i = 2; i < fibonacciCount; ++i) {
fibonacciSequence[i] = fibonacciSequence[i-2] + fibonacciSequence[i-1];
}
}
/* do a binary search for the Fibonacci numbers >= N and <= N */
static void find_closest_fibonacci(uint64_t N, uint64_t *P, uint64_t *Q) {
int upper = fibonacciCount;
int lower = 0;
do {
int mid = ((upper - lower) >> 1) + lower;
uint64_t midValue = fibonacciSequence[mid];
if ( midValue > N ) {
upper = mid;
} else if ( midValue < N ) {
lower = mid + 1;
} else {
*P = fibonacciSequence[ mid ];
*Q = fibonacciSequence[ mid ];
return;
}
} while ( upper > lower );
*P = fibonacciSequence[ lower - 1 ];
*Q = fibonacciSequence[ lower ];
}
/* hacked together 64 bit random number generator,
used just in tester only */
static uint64_t rand64(void) {
/* totally flawed as a random number generator,
but that's not the point here. */
uint64_t v = 0;
for (int i = 0; i < 8; ++i) {
v = (v << 8) + (rand() % 256);
}
return v;
}
int main (int argc, const char * argv[]) {
srand( (unsigned)time( NULL ) );
precalc(); /* do this once only */
uint64_t upperBound = fibonacciSequence[fibonacciCount - 1];
printf( "Upper bound is %qu\n", upperBound );
/* build a sample to run against the algorithm
we favor mostly numbers below RAND_MAX, because
if we test across all of UINT64_MAX the results are
pretty boring. */
static const int sampleCount = 100;
static const int normalSampleCount = 90;
uint64_t numbers[sampleCount];
for (int i = 0; i < normalSampleCount; ++i) {
numbers[i] = rand();
}
for (int i = normalSampleCount; i < sampleCount; ++i) {
uint64_t number;
do {
number = rand64();
} while ( number > upperBound );
numbers[i] = number;
}
/* use described algorithm */
for (int i = 0; i < 100; ++i) {
uint64_t P;
uint64_t Q;
uint64_t N = numbers[i];
find_closest_fibonacci(N, &P, &Q);
printf( "%qu [%qu,%qu]\n", N, P, Q );
}
return 0;
}
Put whatever other algorithm you have in the same file, and run it against the same tester.
You can use the closed-form expression of the fibonacci numbers.
Since the second term in it is very small, you can approximate it with just the first term, so n
can be found with base-golden ratio logarithm.
I posted a complete Proof-Of-Concept implementation of this on https://ideone.com/H6SAd
.
#include <cmath>
#include <iostream>
const double pheta = 0.5*(std::sqrt(5)+1);
double fib(unsigned int n)
{
return (std::pow(pheta, n) - std::pow(1 - pheta, n)) / std::sqrt(5);
}
unsigned int fibo_lowerbound(double N, unsigned min=0, unsigned max=1000)
{
unsigned newpivot = (min+max)/2;
if (min==newpivot)
return newpivot;
if (fib(newpivot) <= N)
return fibo_lowerbound(N, newpivot, max);
else
return fibo_lowerbound(N, min, newpivot);
}
std::pair<double, double> fibo_range(unsigned int n)
{
unsigned int lbound = fibo_lowerbound(n);
return std::make_pair(fib(lbound), fib(lbound+1));
}
void display(unsigned int n)
{
std::pair<double, double> range = fibo_range(n);
std::cout << "Fibonacci range wrapping " << n << " is "
<< "[" << (unsigned long long) range.first << ", " << (unsigned long long) range.second << "]"
<< std::endl;
}
int main()
{
display(1044);
display(8999913);
display(7);
display(67);
}
The output is:
Fibonacci range wrapping 1044 is [987, 1597]
Fibonacci range wrapping 8999913 is [5702887, 9227465]
Fibonacci range wrapping 7 is [5, 8]
Fibonacci range wrapping 67 is [55, 89]
Using the last form here for the inverse you can find the two indexes for the Fib numbers around the current number. http://en.wikipedia.org/wiki/Fibonacci_number#Computation_by_rounding
log(N * sqrt(5)) / log((1+sqrt(5))/2)
should give you a number which is between the two integer indexes for P
and Q
. You can then used the closed-form (as shown in the other answers) to give the actual numbers P
and Q
.
Note that you may be off by one depending on your initial Fib conditions.