Large integers in memorable recursive fibonacci (Y)
I wrote version Y that automatically caches old values ββin a closure using memoization.
var Y = function (f, cache) {
cache = cache || {};
return function (x) {
if (x in cache) return cache[x];
var result = f(function (n) {
return Y(f, cache)(n);
})(x);
return cache[x] = result;
};
};
Now when almostFibonacci
(defined below) is passed into the above function, it returns a large Fibonacci number comfortably.
var almostFibonacci = function (f) {
return function (n) {
return n === '0' || n === '1' ? n : f(n - 1) + f(n - 2);
};
};
However, after a certain value ( Number.MAX_SAFE_INTEGER
), integers in JavaScript (due to their IEEE-754 double precision format ) are inaccurate. So, given the fact that the only math operations in the Fibonacci function above are additions and subtractions, and since the operators cannot be overloaded in JavaScript, I wrote naive implementations of sum and difference functions (which use strings to support large integers) that are in the following.
String.prototype.reverse = function () {
return this.split('').reverse().join('');
};
var difference = function (first, second) {
first = first.reverse();
second = second.reverse();
var firstDigit,
secondDigit,
differenceDigits = [],
differenceDigit,
carry = 0,
index = 0;
while (index < first.length || index < second.length || carry !== 0) {
firstDigit = index < first.length ? parseInt(first[index], 10) : 0;
secondDigit = index < second.length ? parseInt(second[index], 10) : 0;
differenceDigit = firstDigit - secondDigit - carry;
differenceDigits.push((differenceDigit + (differenceDigit < 0 ? 10 : 0)).toString());
carry = differenceDigit < 0 ? 1 : 0;
index++;
}
differenceDigits.reverse();
while (differenceDigits[0] === '0') differenceDigits.shift();
return differenceDigits.join('');
};
var sum = function (first, second) {
first = first.reverse();
second = second.reverse();
var firstDigit,
secondDigit,
sumDigits = [],
sumDigit,
carry = 0,
index = 0;
while (index < first.length || index < second.length || carry !== 0) {
firstDigit = index < first.length ? parseInt(first[index], 10) : 0;
secondDigit = index < second.length ? parseInt(second[index], 10) : 0;
sumDigit = firstDigit + secondDigit + carry;
sumDigits.push((sumDigit % 10).toString());
carry = sumDigit > 9 ? 1 : 0;
index++;
}
sumDigits.reverse();
while (sumDigits[0] === '0') sumDigits.shift();
return sumDigits.join('');
};
Both of these functions now work fine on their own. 1
I have now updated the function almostFibonacci
as follows to use the sum function instead of + and the difference function instead of - .
var almostFibonacci = function (f) {
return function (n) {
return n === '0' || n === '1' ? n : sum(f(difference(n, '1')), f(difference(n, '2')));
};
};
As you may have guessed, this works. It breaks the violin in the case of even a small number such as 10.
Question: What could be wrong? All functions here work great individually. But in tandem, they seem to fail. Can anyone here help me debug this particularly difficult scenario?
1 Except for the case of the edge for the difference function. This requires the first argument to be greater than the second.
Now, on their own, both of these functions work fine - except for the edge case for the difference function. This requires the first argument to be greater than the second.
And what a problem. In your fibonacci algorithm, you calculate at some point difference("2", "2")
which should work "0"
. However, it returns an empty string ""
, which is not checked as a protection condition for recursion. When at the next stage of the calculation difference("", "1")
, the function will end up in an infinite loop.
Solutions:
- Fix that extreme edge (you don't have to deal with negative numbers anyway)
-
Don't use strings for the ordinal, but only for the fibonacci number itself. You are unlikely to try to calculate the number (2 53 +1) th fibonacci, right? I would guess that this is also a significant improvement in speed.
var fibonacci = Y(function(fib) { return function(n) { if (n == 0) return "0"; if (n == 1) return "1"; return sum(fib(n-1), fib(n-2)); }; });
This is how I solved the problem.
Changes
- I removed the operator
while (differenceDigits[0] === '0') differenceDigits.shift();
. Although this outputs the differences without truncated leading zeros, it outputs'0'
in the case of an edge case such asdifference('2', '2')
. - I edited the return statement in the function
almostFibonacci
beforereturn n == 0 || n == 1 ? n : sum(f(difference(n, '1')), f(difference(n, '2')));
. Note that I am checking for 0, not "0" with the lax equality operator. 1
1 The reason that I'm doing n == 0
, as opposed to n === '0'
, is that in JavaScript, '00000' == 0
, but '00000' !== '0'
and in my new updated features differences, truncated without leading zeros, I can not guarantee the number of zeros to the zero output. Well, actually I can. There would be as many zeros as length n.
100th Fibonacci - JSFiddle