I was playing around with writing a javascript Fibonacci sequence, as I had never tried to do so, came up with an easy iterative formula to calculate it. Then I decided to test run it by doing 10000 iterations to see the results. to my surprise, it worked until the 1476th iteration, then broke. 1477 and 1478 both gave the result "Infinity". I have tried different browsers, changing the methods for display, but ended up with the same results.
1475i - 1.3069892237633983e+308
1476i - Infinity
1477i - Infinity
1478i - NaN
Code used:
<!DOCTYPE html><html><head><script>
function fibonacci(){
var x = 1;
var y = 0;
for(i=0;i<1478;i++){
var box = document.createElement('div');
box.setAttribute('id','box'+i);
document.body.appendChild(box);
document.getElementById('box'+i).innerHTML = [i] + 'i - ' + x;
x = x + y;
y = x - y;
}
}
</script></head><body onLoad="fibonacci();"><div id="output"></div></body></html>
I am not sure if then function broke at a certain point, or what I may have failed to take into accountof in the sequence. And yes, I realize that I skipped the first integer, but that shouldn't affect the function.
The 1477th Fibonacci number is too big to be represented by Javascript. The "overflow" causes your number to become Infinity.
Infinity - 1476thFibonacciNumber is still Infinity in the following y calculation.
Then on the next iteration you have Infinity - Infinity which is NaN in JavaScript. From that point, it's NaN all the way to the end.
The largest value Javascript can handle is 1.7976931348623157e+308. If your code generates anything larger than this it will break.
For large values of N, the Fibonacci series can be approximated by
F(N) = math.pow(phi, N) / math.sqrt(5)
(ref: http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibFormula.html#fibround )
Where phi is the golden ratio: (sqrt(5)+1)/2
You can now figure out what the largest Fibonacci number is that you can calculate with the following:
phi = (math.sqrt(5)+1)/2
fibMax = math.floor(log(1.79769)/log(phi) + math.log(math.sqrt(5)))
And the answer from the above is... 1475 - which is the largest number you were able to compute without overflow.
Bottom line - once your calculation overflows, it will continue to do so. Infinity + anything = Infinity, and interestingly Infinity - anything = still infinity. And infinity + infinity = NaN. So even if you subtracted the last number off again, you would not get back to a "real" number. That's just how overflow is treated.
On the subject of the Fibonacci sequence in JavaScript. MDNhas a good example using generators:
// Declare generator
function* fibonacci() {
let n0;
let n1 = 0
let n2 = 1
while (true) {
n0 = n1
n1 = n2
n2 = n0 + n1
yield n0
}
}
// Create generator
var y = fibonacci()
// Print them out
for (let x = 0; x < 1477; x++) {
console.log(x + 1, " ", y.next().value)
}
Related
For example, if my function was called getlowestfraction(), this is what I expect it to do:
getlowestfraction(0.5) // returns 1, 2 or something along the lines of that
Another example:
getlowestfraction(0.125) // returns 1, 8 or something along the lines of that
Using Continued Fractions one can efficiently create a (finite or infinite) sequence of fractions hn/kn that are arbitrary good approximations to a given real number x.
If x is a rational number, the process stops at some point with hn/kn == x. If x is not a rational number, the sequence hn/kn, n = 0, 1, 2, ... converges to x very quickly.
The continued fraction algorithm produces only reduced fractions (nominator and denominator are relatively prime), and the fractions are in
some sense the "best rational approximations" to a given real number.
I am not a JavaScript person (programming in C normally), but I have tried to implement the algorithm with the following JavaScript function. Please forgive me if there are stupid errors. But I have checked the function and it seems to work correctly.
function getlowestfraction(x0) {
var eps = 1.0E-15;
var h, h1, h2, k, k1, k2, a, x;
x = x0;
a = Math.floor(x);
h1 = 1;
k1 = 0;
h = a;
k = 1;
while (x-a > eps*k*k) {
x = 1/(x-a);
a = Math.floor(x);
h2 = h1; h1 = h;
k2 = k1; k1 = k;
h = h2 + a*h1;
k = k2 + a*k1;
}
return h + "/" + k;
}
The loop stops when the rational approximation is exact or has the given precision eps = 1.0E-15. Of course, you can adjust the precision to your needs. (The while condition is derived from the theory of continued fractions.)
Examples (with the number of iterations of the while-loop):
getlowestfraction(0.5) = 1/2 (1 iteration)
getlowestfraction(0.125) = 1/8 (1 iteration)
getlowestfraction(0.1+0.2) = 3/10 (2 iterations)
getlowestfraction(1.0/3.0) = 1/3 (1 iteration)
getlowestfraction(Math.PI) = 80143857/25510582 (12 iterations)
Note that this algorithm gives 1/3 as approximation for x = 1.0/3.0. Repeated multiplication of x by powers of 10 and canceling common factors would give something like 3333333333/10000000000.
Here is an example of different precisions:
With eps = 1.0E-15 you get getlowestfraction(0.142857) = 142857/1000000.
With eps = 1.0E-6 you get getlowestfraction(0.142857) = 1/7.
You could keep multiplying by ten until you have integer values for your numerator and denominator, then use the answers from this question to reduce the fraction to its simplest terms.
Try this program instead:
function toFrac(number) {
var fractional = number % 1;
if (fractional) {
var real = number - fractional;
var exponent = String(fractional).length - 2;
var denominator = Math.pow(10, exponent);
var mantissa = fractional * denominator;
var numerator = real * denominator + mantissa;
var gcd = GCD(numerator, denominator);
denominator /= gcd;
numerator /= gcd;
return [numerator, denominator];
} else return [number, 1];
}
function gcd(numerator, denominator) {
do {
var modulus = numerator % denominator;
numerator = denominator;
denominator = modulus;
} while (modulus);
return numerator;
}
Then you may use it as follows:
var start = new Date;
var PI = toFrac(Math.PI);
var end = new Date;
alert(PI);
alert(PI[0] / PI[1]);
alert(end - start + " ms");
You can see the demo here: http://jsfiddle.net/MZaK9/1/
Was just fiddling around with code, and got the answer myself:
function getlowestfraction (num) {
var i = 1;
var mynum = num;
var retnum = 0;
while (true) {
if (mynum * i % 1 == 0) {
retnum = mynum * i;
break;
}
// For exceptions, tuned down MAX value a bit
if (i > 9000000000000000) {
return false;
}
i++;
}
return retnum + ", " + i;
}
In case anybody needed it.
P.S: I'm not trying to display my expertise or range of knowledge. I actually did spend a long time in JSFiddle trying to figure this out (well not a really long time anyway).
Suppose the number is x = 0 . ( a_1 a_2 ... a_k ) ( a_1 a_2 ... a_k ) .... for simplicity (keep in mind that the first few digits may not fit the repeating pattern, and that we need a way to figure out what k is). If b is the base, then
b ^ k * x - x = ( b ^ k - 1 ) * x
on one hand, but
b ^ k * x - x = ( a_1 a_2 ... a_k )
(exact, ie this is an integer) on the other hand.
So
x = ( a_1 ... a_k ) / ( b ^ k - 1 )
Now you can use Euclid's algorithm to get the gcd and divide it out to get the reduced fraction.
You would still have to figure out how to determine the repeating sequence. There should be an answer to that question. EDIT - one answer: it's the length of \1 if there's a match to the pattern /([0-9]+)\1+$/ (you might want to throw out the last digit before matching bc of rounding). If there's no match, then there's no better "answer" than the "trivial" representation" (x*base^precision/base^precision).
N.B. This answer makes some assumptions on what you expect of an answer, maybe not right for your needs. But it's the "textbook" way of getting reproducing the fraction from a repeating decimal representation - see e.g. here
A very old but a gold question which at the same time an overlooked one. So i will go and mark this popular one as a duplicate with hopes that new people end up at the correct place.
The accepted answer of this question is a gem of the internet. No library that i am aware of uses this magnificient technique and ends up with not wrong but silly rationals. Having said that, the accepted answer is not totally correct due to several issues like;
What exactly is happening there?
Why it still returns '140316103787451/7931944815571' instead of '1769/100' when the input is 17.69?
How do you decide when to stop the while loop?
Now the most important question is, what's happening there and howcome this algorithm is so very efficient.
We must know that any number can also be expressed as a continuous fraction. Say you are given 0.5. You can express it like
1
0 + ___ // the 0 here is in fact Math.floor(0.5)
2 // the 2 here is in fact Math.floor(1/0.5)
So say you are given 2.175 then you end up with
1
2 + _______________ // the 2 here is in fact Math.floor(2.175)
1
5 + ___________ // the 5 here is in fact Math.floor(1/0.175 = 5.714285714285714)
1
1 + _______ // the 1 here is in fact Math.floor(1/0.714285714285714 = 1.4)
1
2 + ___ // the 2 here is in fact Math.floor(1/0.4 = 2.5)
2 // the 2 here is in fact Math.floor(1/0.5)
We now have our continued fraction coefficients like [2;5,1,2,2] for 2.175. However the beauty of this algorithm lies behind how it calculates the approximation at once when we calculate the next continued fraction constant without requiring any further calculations. At this very moment we can compare the currently reached result with the given value and decide to stop or iterate once more.
So far so good however it still doesn't make sense right? Let us go with another solid example. Input value is 3.686635944700461. Now we are going to approach this from Infinity and very quickly converge to the result. So our first rational is 1/0 aka Infinity. We denote this as a fraction with a numerator p as 1 and denominator q as 0 aka 1/0. The previous approximation would be p_/q_ for the next stage. Let us make it 0 to start with. So p_ is 0 and q_ is 1.
The important part is, once we know the two previous approximations, (p, q, p_ and q_) we can then calculate the next coefficient m and also the next p and q to compare with the input. Calculating the coefficient m is as simple as Math.floor(x_) whereas x_ is reciprocal of the next floating part. The next approximation p/q would then be (m * p + p_)/(m * q + q_) and the next p_/q_ would be the previous p/q. (Theorem 2.4 # this paper)
Now given above information any decent programmer can easily resolve the following snippet. For curious, 3.686635944700461 is 800/217 and gets calculated in just 5 iterations by the below code.
function toRational(x){
var m = Math.floor(x),
x_ = 1/(x-m),
p_ = 1,
q_ = 0,
p = m,
q = 1;
if (x === m) return {n:p,d:q};
while (Math.abs(x - p/q) > Number.EPSILON){
m = Math.floor(x_);
x_ = 1/(x_-m);
[p_, q_, p, q] = [p, q, m*p+p_, m*q+q_];
}
return isNaN(x) ? NaN : {n:p,d:q};
}
Under practical considerations it would be ideal to store the coefficients in the fraction object as well so that in future you may use them to perform CFA (Continuous Fraction Arithmetics) among rationals. This way you may avoid huge integers and possible BigInt usage by staying in the CF domain to perform invertion, negation, addition and multiplication operations. Sadly, CFA is a very overlooked topic but it helps us to avoid double precision errors when doing cascaded arithmetic operations on the rational type values.
For an odds calculator for a board game, I need to calculate how many rounds a battle will last on average. Because there is a possibility that both sides in the battle will miss, a battle can theoretically last forever. Therefore I cannot traverse all branches, but need to calculate a mathematical limit. By verifying with a simulator, I have found that the following function correctly approximates the average number of rounds left:
// LIMIT could be any number, the larger it is, the more accurate the result.
const LIMIT = 100;
// r is the number of rounds left if at least 1 of the sides hit
// x is the chance that both sides miss and the round count gets increased,
// but the battle state stays the same.
function approximateLimitForNumberOfRounds(r: number, x: number) {
let approx = r / (1 - x);
// n -> infinity
for (let n = 1; n < LIMIT; n++) {
approx += x ** n;
}
return approx;
}
How can I modify this function to exactly calculate the number of rounds left, instead of approximating it? (noting that since x is a chance, it is contained in (0, 1) or 0 < x < 1).
We can note that approx takes on the following values:
r / (1 - x) # I refer to this as 'a' below
a + x
a + x + x^2
a + x + x^2 + x^3
a + x + x^2 + ... + x^n
Thus, we can simplify the mathematical expression to be:
a + (the sum of x^k from k = 1 to k = n)
Next, we must note that the sequence x + x^2 + x^3 ... forms a geometric sequence with first term x and common ratio x. Since x is bounded by 0 < x < 1, this will have a limiting sum, namely:
x + x^2 + x^3 + ... x^inf = x/(1-x)
(this obviously fails when x = 1, as well as in the original function where r / (1 - x) is taken, but in that case, you will simply have the sum as infinity and approx would escape to infinity if it were not undefined; so I am assuming that x != 1 in the following calculations and x = 1 can be / has been dealt with separately).
Now, since we have both a single expression for x + x^2 + ... to infinity, and a single expression for approx that includes x + x^2 + ... then we can write approx using both of these two facts:
approx = r / (1 - x) + x / (1 - x)
approx = (r + x) / (1 - x)
And there you go! That is the mathematical equivalent of the logic you've outlined in your question, compressed to a single statement (which I believe is correct :)).
I'm trying to solve all the lessons on codility but I failed to do so on the following problem: Ladder by codility
I've searched all over the internet and I'm not finding a answer that satisfies me because no one answers why the max variable impacts so much the result.
So, before posting the code, I'll explain the thinking.
By looking at it I didn't need much time to understand that the total number of combinations it's a Fibonacci number, and removing the 0 from the Fibonacci array, I'd find the answer really fast.
Now, afterwards, they told that we should return the number of combinations modulus 2^B[i].
So far so good, and I decided to submit it without the var max, then I got a score of 37%.. I searched all over the internet and the 100% result was similar to mine but they added that max = Math.pow(2,30).
Can anyone explain to me how and why that max influences so much the score?
My Code:
// Powers 2 to num
function pow(num){
return Math.pow(2,num);
}
// Returns a array with all fibonacci numbers except for 0
function fibArray(num){
// const max = pow(30); -> Adding this max to the fibonaccy array makes the answer be 100%
const arr = [0,1,1];
let current = 2;
while(current<=num){
current++;
// next = arr[current-1]+arr[current-2] % max;
next = arr[current-1]+arr[current-2]; // Without this max it's 30 %
arr.push(next);
}
arr.shift(); // remove 0
return arr;
}
function solution(A, B) {
let f = fibArray(A.length + 1);
let res = new Array(A.length);
for (let i = 0; i < A.length; ++i) {
res[i] = f[A[i]] % (pow(B[i]));
}
return res;
}
console.log(solution([4,4,5,5,1],[3,2,4,3,1])); //5,1,8,0,1
// Note that the console.log wont differ in this solution having max set or not.
// Running the exercise on Codility shows the full log with all details
// of where it passed and where it failed.
The limits for input parameters are:
Assume that:
L is an integer within the range [1..50,000];
each element of array A is an integer within the range [1..L];
each element of array B is an integer within the range [1..30].
So the array f in fibArray can be 50,001 long.
Fibonacci numbers grow exponentially; according to this page, the 50,000th Fib number has over 10,000 digits.
Javascript does not have built-in support for arbitrary precision integers, and even doubles only offer ~14 s.f. of precision. So with your modified code, you will get "garbage" values for any significant value of L. This is why you only got 30%.
But why is max necessary? Modulo math tells us that:
(a + b) % c = ([a % c] + [b % c]) % c
So by applying % max to the iterative calculation step arr[current-1] + arr[current-2], every element in fibArray becomes its corresponding Fib number mod max, without any variable exceeding the value of max (or built-in integer types) at any time:
fibArray[2] = (fibArray[1] + fibArray[0]) % max = (F1 + F0) % max = F2 % max
fibArray[3] = (F2 % max + F1) % max = (F2 + F1) % max = F3 % max
fibArray[4] = (F3 % max + F2 % max) = (F3 + F2) % max = F4 % max
and so on ...
(Fn is the n-th Fib number)
Note that as B[i] will never exceed 30, pow(2, B[i]) <= max; therefore, since max is always divisible by pow(2, B[i]), applying % max does not affect the final result.
Here is a python 100% answer that I hope offers an explanation :-)
In a nutshell; modulus % is similar to 'bitwise and' & for certain numbers.
eg any number % 10 is equivalent to the right most digit.
284%10 = 4
1994%10 = 4
FACTS OF LIFE:
for multiples of 2 -> X % Y is equivalent to X & ( Y - 1 )
precomputing (2**i)-1 for i in range(1, 31) is faster than computing everything in B when super large arrays are given as args for this particular lesson.
Thus fib(A[i]) & pb[B[i]] will be faster to compute than an X % Y style thingy.
https://app.codility.com/demo/results/trainingEXWWGY-UUR/
And for completeness the code is here.
https://github.com/niall-oc/things/blob/master/codility/ladder.py
Here is my explanation and solution in C++:
Compute the first L fibonacci numbers. Each calculation needs modulo 2^30 because the 50000th fibonacci number cannot be stored even in long double, it is so big. Since INT_MAX is 2^31, the summary of previously modulo'd numbers by 2^30 cannot exceed that. Therefore, we do not need to have bigger store and/or casting.
Go through the arrays executing the lookup and modulos. We can be sure this gives the correct result since modulo 2^30 does not take any information away. E.g. modulo 100 does not take away any information for subsequent modulo 10.
vector<int> solution(vector<int> &A, vector<int> &B)
{
const int L = A.size();
vector<int> fibonacci_numbers(L, 1);
fibonacci_numbers[1] = 2;
static const int pow_2_30 = pow(2, 30);
for (int i = 2; i < L; ++i) {
fibonacci_numbers[i] = (fibonacci_numbers[i - 1] + fibonacci_numbers[i - 2]) % pow_2_30;
}
vector<int> consecutive_answers(L, 0);
for (int i = 0; i < L; ++i) {
consecutive_answers[i] = fibonacci_numbers[A[i] - 1] % static_cast<int>(pow(2, B[i]));
}
return consecutive_answers;
}
So I was calculating last ten digit of the series :
1^1 + 2^2 + 3^3 + ... + 1000^1000
But I keep getting NaN as a result.
Code:
function myFunction() {
var i, x, a, sum = 0; {
for (i = 1; i <= 1000; i++) {
var a = Math.pow(i, i);
sum += a;
}
var x = sum;
var y = x % 10000000000;
}
document.getElementById("demo").innerHTML = y;
}
<p>Click the button to demontrate </p>
<button onclick="myFunction()">Try it</button>
<p id="demo"></p>
Math.pow(1000, 1000)
Thats a very very huge number, (3000 zeros), so javascript can't handle it, thats why the above results in Infinity. And the modulo of an infinite number cannot be determined, therefore the result is Not A Number.
You have an Infinity value - try the code below :
function myFunction() {
var i, x, a, sum = 0; {
for (i = 1; i <= 1000; i++) {
var a = Math.pow(i, i);
if(a!=Infinity)sum += a;
}
var x = sum;
var y = x % 10000000000;
document.getElementById("demo").innerHTML = y;
}
Javascript uses 64-bit floating point numbers for everything. The max value of one of those is about 1.8 × 10^308, which is quite a bit smaller than your 1000^1000 value. As a consequence, since the number it's trying to calculate can't fit into a "number"-type variable, Javascript returns NaN and leaves it at that.
Number.MAX_VALUE is the maximum number available.
Otherwise you have to use some library for example: https://mikemcl.github.io/bignumber.js/
Explanation
Big Ints (greater than (2^31 - 1) as #suhas explained) are not currently supported in JS, however there is a TC39 draft currently in stage 3 (candidate).
Solution
In the meantime, use a library such as this one.
As #jonas-w correctly pointed out, your specific problem is trying to take the modulo of an infinite number. infinity % anyNumber yields NaN.
In addition, you need to understand that floating-point values use scientific notation. That means that for large numbers, all of your significant digits (53 for IEEE 754 double-precision, which is what JavaScript uses) are at the upper end of the value, yet your modulo is examining the lower end.
For example, 1.1×10^6 is 1,100,000, and 1.2×10^6 is 1,200,000. Getting the % 100 is pointless, as both yield 0.
What are you trying to accomplish with this code?
For example, if my function was called getlowestfraction(), this is what I expect it to do:
getlowestfraction(0.5) // returns 1, 2 or something along the lines of that
Another example:
getlowestfraction(0.125) // returns 1, 8 or something along the lines of that
Using Continued Fractions one can efficiently create a (finite or infinite) sequence of fractions hn/kn that are arbitrary good approximations to a given real number x.
If x is a rational number, the process stops at some point with hn/kn == x. If x is not a rational number, the sequence hn/kn, n = 0, 1, 2, ... converges to x very quickly.
The continued fraction algorithm produces only reduced fractions (nominator and denominator are relatively prime), and the fractions are in
some sense the "best rational approximations" to a given real number.
I am not a JavaScript person (programming in C normally), but I have tried to implement the algorithm with the following JavaScript function. Please forgive me if there are stupid errors. But I have checked the function and it seems to work correctly.
function getlowestfraction(x0) {
var eps = 1.0E-15;
var h, h1, h2, k, k1, k2, a, x;
x = x0;
a = Math.floor(x);
h1 = 1;
k1 = 0;
h = a;
k = 1;
while (x-a > eps*k*k) {
x = 1/(x-a);
a = Math.floor(x);
h2 = h1; h1 = h;
k2 = k1; k1 = k;
h = h2 + a*h1;
k = k2 + a*k1;
}
return h + "/" + k;
}
The loop stops when the rational approximation is exact or has the given precision eps = 1.0E-15. Of course, you can adjust the precision to your needs. (The while condition is derived from the theory of continued fractions.)
Examples (with the number of iterations of the while-loop):
getlowestfraction(0.5) = 1/2 (1 iteration)
getlowestfraction(0.125) = 1/8 (1 iteration)
getlowestfraction(0.1+0.2) = 3/10 (2 iterations)
getlowestfraction(1.0/3.0) = 1/3 (1 iteration)
getlowestfraction(Math.PI) = 80143857/25510582 (12 iterations)
Note that this algorithm gives 1/3 as approximation for x = 1.0/3.0. Repeated multiplication of x by powers of 10 and canceling common factors would give something like 3333333333/10000000000.
Here is an example of different precisions:
With eps = 1.0E-15 you get getlowestfraction(0.142857) = 142857/1000000.
With eps = 1.0E-6 you get getlowestfraction(0.142857) = 1/7.
You could keep multiplying by ten until you have integer values for your numerator and denominator, then use the answers from this question to reduce the fraction to its simplest terms.
Try this program instead:
function toFrac(number) {
var fractional = number % 1;
if (fractional) {
var real = number - fractional;
var exponent = String(fractional).length - 2;
var denominator = Math.pow(10, exponent);
var mantissa = fractional * denominator;
var numerator = real * denominator + mantissa;
var gcd = GCD(numerator, denominator);
denominator /= gcd;
numerator /= gcd;
return [numerator, denominator];
} else return [number, 1];
}
function gcd(numerator, denominator) {
do {
var modulus = numerator % denominator;
numerator = denominator;
denominator = modulus;
} while (modulus);
return numerator;
}
Then you may use it as follows:
var start = new Date;
var PI = toFrac(Math.PI);
var end = new Date;
alert(PI);
alert(PI[0] / PI[1]);
alert(end - start + " ms");
You can see the demo here: http://jsfiddle.net/MZaK9/1/
Was just fiddling around with code, and got the answer myself:
function getlowestfraction (num) {
var i = 1;
var mynum = num;
var retnum = 0;
while (true) {
if (mynum * i % 1 == 0) {
retnum = mynum * i;
break;
}
// For exceptions, tuned down MAX value a bit
if (i > 9000000000000000) {
return false;
}
i++;
}
return retnum + ", " + i;
}
In case anybody needed it.
P.S: I'm not trying to display my expertise or range of knowledge. I actually did spend a long time in JSFiddle trying to figure this out (well not a really long time anyway).
Suppose the number is x = 0 . ( a_1 a_2 ... a_k ) ( a_1 a_2 ... a_k ) .... for simplicity (keep in mind that the first few digits may not fit the repeating pattern, and that we need a way to figure out what k is). If b is the base, then
b ^ k * x - x = ( b ^ k - 1 ) * x
on one hand, but
b ^ k * x - x = ( a_1 a_2 ... a_k )
(exact, ie this is an integer) on the other hand.
So
x = ( a_1 ... a_k ) / ( b ^ k - 1 )
Now you can use Euclid's algorithm to get the gcd and divide it out to get the reduced fraction.
You would still have to figure out how to determine the repeating sequence. There should be an answer to that question. EDIT - one answer: it's the length of \1 if there's a match to the pattern /([0-9]+)\1+$/ (you might want to throw out the last digit before matching bc of rounding). If there's no match, then there's no better "answer" than the "trivial" representation" (x*base^precision/base^precision).
N.B. This answer makes some assumptions on what you expect of an answer, maybe not right for your needs. But it's the "textbook" way of getting reproducing the fraction from a repeating decimal representation - see e.g. here
A very old but a gold question which at the same time an overlooked one. So i will go and mark this popular one as a duplicate with hopes that new people end up at the correct place.
The accepted answer of this question is a gem of the internet. No library that i am aware of uses this magnificient technique and ends up with not wrong but silly rationals. Having said that, the accepted answer is not totally correct due to several issues like;
What exactly is happening there?
Why it still returns '140316103787451/7931944815571' instead of '1769/100' when the input is 17.69?
How do you decide when to stop the while loop?
Now the most important question is, what's happening there and howcome this algorithm is so very efficient.
We must know that any number can also be expressed as a continuous fraction. Say you are given 0.5. You can express it like
1
0 + ___ // the 0 here is in fact Math.floor(0.5)
2 // the 2 here is in fact Math.floor(1/0.5)
So say you are given 2.175 then you end up with
1
2 + _______________ // the 2 here is in fact Math.floor(2.175)
1
5 + ___________ // the 5 here is in fact Math.floor(1/0.175 = 5.714285714285714)
1
1 + _______ // the 1 here is in fact Math.floor(1/0.714285714285714 = 1.4)
1
2 + ___ // the 2 here is in fact Math.floor(1/0.4 = 2.5)
2 // the 2 here is in fact Math.floor(1/0.5)
We now have our continued fraction coefficients like [2;5,1,2,2] for 2.175. However the beauty of this algorithm lies behind how it calculates the approximation at once when we calculate the next continued fraction constant without requiring any further calculations. At this very moment we can compare the currently reached result with the given value and decide to stop or iterate once more.
So far so good however it still doesn't make sense right? Let us go with another solid example. Input value is 3.686635944700461. Now we are going to approach this from Infinity and very quickly converge to the result. So our first rational is 1/0 aka Infinity. We denote this as a fraction with a numerator p as 1 and denominator q as 0 aka 1/0. The previous approximation would be p_/q_ for the next stage. Let us make it 0 to start with. So p_ is 0 and q_ is 1.
The important part is, once we know the two previous approximations, (p, q, p_ and q_) we can then calculate the next coefficient m and also the next p and q to compare with the input. Calculating the coefficient m is as simple as Math.floor(x_) whereas x_ is reciprocal of the next floating part. The next approximation p/q would then be (m * p + p_)/(m * q + q_) and the next p_/q_ would be the previous p/q. (Theorem 2.4 # this paper)
Now given above information any decent programmer can easily resolve the following snippet. For curious, 3.686635944700461 is 800/217 and gets calculated in just 5 iterations by the below code.
function toRational(x){
var m = Math.floor(x),
x_ = 1/(x-m),
p_ = 1,
q_ = 0,
p = m,
q = 1;
if (x === m) return {n:p,d:q};
while (Math.abs(x - p/q) > Number.EPSILON){
m = Math.floor(x_);
x_ = 1/(x_-m);
[p_, q_, p, q] = [p, q, m*p+p_, m*q+q_];
}
return isNaN(x) ? NaN : {n:p,d:q};
}
Under practical considerations it would be ideal to store the coefficients in the fraction object as well so that in future you may use them to perform CFA (Continuous Fraction Arithmetics) among rationals. This way you may avoid huge integers and possible BigInt usage by staying in the CF domain to perform invertion, negation, addition and multiplication operations. Sadly, CFA is a very overlooked topic but it helps us to avoid double precision errors when doing cascaded arithmetic operations on the rational type values.