19

In JavaScript, is there any way to convert a decimal number (such as 0.0002) to a fraction represented as a string (such as "2/10000")?

If a function called decimalToFraction had been written for this purpose, then decimalToFraction(0.0002) would return the string "2/10000".

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Anderson Green
  • 30,230
  • 67
  • 195
  • 328
  • 1
    This question has a very similar title, but it's actually not a duplicate of this one (since it's about a different topic). http://stackoverflow.com/questions/7249195/convert-decimal-amount-to-text-string-fraction-in-javascript?rq=1 – Anderson Green Feb 09 '13 at 01:14
  • Does the fraction need to be in lowest terms (2/10000 == 1/5000)? – Jacob Dalton Feb 09 '13 at 01:19
  • @JacobDalton Yes, it would be useful to convert the fraction to lowest terms. There's already an answer on Stack Overflow that explains how to simplify fractions in JavaScript, but I don't remember the question's title. – Anderson Green Feb 09 '13 at 01:20
  • 1
    possible duplicate of [Convert Decimal number into Fraction](http://stackoverflow.com/questions/13364425/convert-decimal-number-into-fraction) – Doug Currie Feb 09 '13 at 01:21
  • 1
    @DougCurrie That question is about C, not JavaScript. :/ – Anderson Green Feb 09 '13 at 01:22
  • @Anderson Green, the answer to that question is not language specific. – Doug Currie Feb 09 '13 at 01:23
  • I would try to figure how http://www.mindspring.com/~alanh/fracs.html works. – Alex Wayne Feb 09 '13 at 01:24
  • @Anderson: The code in that questions/answer does not have anything specific to C. It's just math. Not saying that this is a duplicate, but it does not always in which language an algorithm is written. – Felix Kling Feb 09 '13 at 01:24
  • @FelixKling That's true, but the answers to the other question would need to be manually converted to JavaScript in order to be considered relevant here. The two questions can't be considered duplicates of each other unless they are about exactly the same topic. – Anderson Green Feb 09 '13 at 01:30
  • Farey's best rational approximation algorithm with a fuzziness limit: http://stackoverflow.com/a/43016456/191246 – ccpizza Mar 25 '17 at 13:30

12 Answers12

22

You can use Erik Garrison's fraction.js library to do that and more fractional operations.

var f = new Fraction(2, 10000);
console.log(f.numerator + '/' + f.denominator);

To to do .003 you can just do

var f = new Fraction(.003);
console.log(f.numerator + '/' + f.denominator);
Trent Earl
  • 3,517
  • 1
  • 15
  • 20
  • What function from fraction.js should I use to convert a decimal to a fraction (for example, converting `0.003` to a fraction such as `3/1000`)? I still don't know which specific function I should use to convert fractions to decimals. – Anderson Green Feb 09 '13 at 01:31
  • Which JavaScript files should I include on a web page in order to use fraction.js? The documentation doesn't make this clear. – Anderson Green Feb 09 '13 at 07:19
  • 1
    @TRENT f = new Fraction(1.67); outputs as 167/100 but it should be 10/6. How can we achieve this? – Vasanthan.R.P Apr 02 '14 at 05:22
  • 1
    in fraction.js for decimals you can use var f = new Fraction(.003); f.toString() <--- to string will print the decimal in Fraction format. You don't need to build the fraction string your self – Ryand.Johnson Apr 14 '15 at 13:49
  • 1
    Beware that fraction.js seems to ignore javascript floating point issues like `19/11 = 1.7272727272727273` (last digit) which fraction.js then formats to `1 727272727/1000000000`. – Basti Jun 09 '15 at 18:56
8

A little googling with the term "decimal to fraction js" the first yielded this:

http://wildreason.com/wildreason-blog/2010/javascript-convert-a-decimal-into-a-simplified-fraction/

It seems to work:

http://jsfiddle.net/VKfHH/

function HCF(u, v) { 
    var U = u, V = v
    while (true) {
        if (!(U%=V)) return V
        if (!(V%=U)) return U 
    } 
}
//convert a decimal into a fraction
function fraction(decimal){

    if(!decimal){
        decimal=this;
    }
    whole = String(decimal).split('.')[0];
    decimal = parseFloat("."+String(decimal).split('.')[1]);
    num = "1";
    for(z=0; z<String(decimal).length-2; z++){
        num += "0";
    }
    decimal = decimal*num;
    num = parseInt(num);
    for(z=2; z<decimal+1; z++){
        if(decimal%z==0 && num%z==0){
            decimal = decimal/z;
            num = num/z;
            z=2;
        }
    }
    //if format of fraction is xx/xxx
    if (decimal.toString().length == 2 && 
            num.toString().length == 3) {
                //reduce by removing trailing 0's
        decimal = Math.round(Math.round(decimal)/10);
        num = Math.round(Math.round(num)/10);
    }
    //if format of fraction is xx/xx
    else if (decimal.toString().length == 2 && 
            num.toString().length == 2) {
        decimal = Math.round(decimal/10);
        num = Math.round(num/10);
    }
    //get highest common factor to simplify
    var t = HCF(decimal, num);

    //return the fraction after simplifying it
    return ((whole==0)?"" : whole+" ")+decimal/t+"/"+num/t;
}

// Test it
alert(fraction(0.0002)); // "1/5000"
Basti
  • 3,998
  • 1
  • 18
  • 21
Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • Hmmm. Try converting 2.56 to a fraction with that. Should be 2 and 14/25 – Popnoodles Feb 09 '13 at 01:29
  • Heh, yeah. I bet a floating point rounding error is making that strange. – Alex Wayne Feb 09 '13 at 01:33
  • When I tested the number `2.56` as input, I got the following output, which is clearly incorrect: `2 7881299347898369/14073748835532800`. I'll try to find a better solution, if possible. – Anderson Green Feb 09 '13 at 01:57
  • 1
    After some testing, the line multiplying the decimal part in to an integer has the floating point error resulting in 56.0000000001. The solution I found was to simply use substr(2,decimal.length) on the decimal string which gives the correct answer (in my code anyhow) – robjtede Dec 31 '13 at 19:26
  • Unfortunately, the link in the answer is no longer valid. – Jarrett Feb 11 '15 at 23:39
  • Here's improved version of this fiddle: http://jsfiddle.net/matikucharski/x4a8ntj0/ that calculates 2.56 correctly – MatiK Jun 03 '16 at 15:09
7

I used this site Link to build a function but as the article mentions you will get an unreasonable large number for radicals or pi.

Hope it helps though.

function Fraction(){}
Fraction.prototype.convert = function(x, improper)
{
    improper = improper || false;
    var abs = Math.abs(x);
    this.sign = x/abs;
    x = abs;
    var stack = 0;
    this.whole = !improper ? Math.floor(x) : 0;
    var fractional = !improper ? x-this.whole : abs;
    /*recursive function that transforms the fraction*/
    function recurs(x){
        stack++;
        var intgr = Math.floor(x); //get the integer part of the number
        var dec = (x - intgr); //get the decimal part of the number
        if(dec < 0.0019 || stack > 20) return [intgr,1]; //return the last integer you divided by
        var num = recurs(1/dec); //call the function again with the inverted decimal part
        return[intgr*num[0]+num[1],num[0]]
    }
    var t = recurs(fractional); 
    this.numerator = t[0];
    this.denominator = t[1];
}

Fraction.prototype.toString = function()
{
    var l  = this.sign.toString().length;
    var sign = l === 2 ? '-' : '';
    var whole = this.whole !== 0 ? this.sign*this.whole+' ': sign;
    return whole+this.numerator+'/'+this.denominator;
}

//var frac = new Fraction()
//frac.convert(2.56, false)
//console.log(frac.toString())
//use frac.convert(2.56,true) to get it as an improper fraction

If you just want a self-contained function that only returns the numerator and denominator then use the function below.

var toFraction = function (dec) {
    var is_neg = dec < 0;
    dec = Math.abs(dec);
    var done = false;
    //you can adjust the epsilon to a larger number if you don't need very high precision
    var n1 = 0, d1 = 1, n2 = 1, d2 = 0, n = 0, q = dec, epsilon = 1e-13;
    while (!done) {
        n++;
        if (n > 10000) {
            done = true;
        }
        var a = parseInt(q);
        var num = n1 + a * n2;
        var den = d1 + a * d2;
        var e = (q - a);
        if (e < epsilon) {
            done = true;
        }
        q = 1 / e;
        n1 = n2;
        d1 = d2;
        n2 = num;
        d2 = den;
        if (Math.abs(num / den - dec) < epsilon || n > 30) {
            done = true;
        }
    }
    return [is_neg ? -num : num, den];
};
//Usage:
//var frac = toFraction(0.5);
//console.log(frac)
//Output: [ 1, 2 ]
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
jiggzson
  • 515
  • 1
  • 4
  • 13
7

Very old question but maybe someone can find this useful. It's iterative, not recursive and doesn't require factorization

function getClosestFraction(value, tol) {
    var original_value = value;
    var iteration = 0;
    var denominator=1, last_d = 0, numerator;
    while (iteration < 20) {
        value = 1 / (value - Math.floor(value))
        var _d = denominator;
        denominator = Math.floor(denominator * value + last_d);
        last_d = _d;
        numerator = Math.ceil(original_value * denominator)

        if (Math.abs(numerator/denominator - original_value) < tol)
            break;
        iteration++;
    }
    return {numerator: numerator, denominator: denominator};
};
Walter
  • 196
  • 2
  • 4
3

There is a very simple solution using string representation of numbers

    string = function(f){ // returns string representation of an object or number
        return f+"";
    }
    fPart = function(f){ // returns the fraction part (the part after the '.') of a number
        str = string(f);
        return str.indexOf(".")<0?"0":str.substring(str.indexOf(".") + 1);
    }
    wPart = function(f){ // returns the integer part (the part before the '.') of a number
        str = string(f);
        return str.indexOf(".")<0?str:str.substring(0, str.indexOf(".")); // possibility 1
        //return string(f - parseInt(fPart(f))); // just substract the fPart
    }

    power = function(base, exp){
        var tmp = base;
        while(exp>1){
            base*=tmp;
            --exp;
        }
        return base;
    }

    getFraction = function(f){ // the function
        var denominator = power(10, fPart(f).length), numerator = parseInt(fPart(f)) + parseInt(wPart(f))*denominator;
        return "[ " + numerator + ", " + denominator + "]";
    }

    console.log(getFraction(987.23));

which will just check how many numbers are in the fraction and then expands the fraction of f/1 until f is an integer. This can lead to huge fractions, so you can reduce it by dividing both numerator and denominator by the greatest common divisor of both, e.g.

    // greatest common divisor brute force
    gcd = function(x,y){
        for(var i = Math.min(x, y);i>0;i--) if(!(x%i||y%i)) return i;
        return 1;
    }
Chemistree
  • 432
  • 6
  • 11
2

The good news is that it's possible, but you'll have to convert it to code.

Let's go with 2.56 for no reason at all.

Use the decimal portion of the number .56

There are 2 digits in .56, write .56 as 56/100.

So we have 2 + 56/100 and need to reduce this fraction to lowest terms by dividing both the numerator and denominator by the greatest common divisor, which is 4 in this case.

So, this fraction reduced to lowest terms is 2 + 14/25.

To add those whole 2, we multiply by the divisor and add to the 14

(2*25 + 14)/25 = 64/25

Popnoodles
  • 28,090
  • 2
  • 45
  • 53
0

Have tried something like this?

var cnum = 3.5,
  deno = 10000,
  neww;
neww = cnum * deno;
while (!(neww % 2 > 0) && !(deno % 2 > 0)) {
  neww = neww / 2;
  deno = deno / 2;
}
while (!(neww % 3 > 0) && !(deno % 3 > 0)) {
  neww = neww / 3;
  deno = deno / 3;
}
while (!(neww % 5 > 0) && !(deno % 5 > 0)) {
  neww = neww / 5;
  deno = deno / 5;
}
while (!(neww % 7 > 0) && !(deno % 7 > 0)) {
  neww = neww / 7;
  deno = deno / 7;
}
while (!(neww % 11 > 0) && !(deno % 11 > 0)) {
  neww = neww / 11;
  deno = deno / 11;
}
while (!(neww % 13 > 0) && !(deno % 13 > 0)) {
  neww = neww / 13;
  deno = deno / 13;
}
while (!(neww % 17 > 0) && !(deno % 17 > 0)) {
  neww = neww / 17;
  deno = deno / 17;
}
while (!(neww % 19 > 0) && !(deno % 19 > 0)) {
  neww = neww / 19;
  deno = deno / 19;
}
console.log(neww + "/" + deno);
AlexSp3
  • 2,201
  • 2
  • 7
  • 24
Janaka R Rajapaksha
  • 3,585
  • 1
  • 25
  • 28
0

I did what popnoodles suggested and here it is

function FractionFormatter(value) {
  if (value == undefined || value == null || isNaN(value))
    return "";

  function _FractionFormatterHighestCommonFactor(u, v) {
      var U = u, V = v
      while (true) {
        if (!(U %= V)) return V
        if (!(V %= U)) return U
      }
  }

  var parts = value.toString().split('.');
  if (parts.length == 1)
    return parts;
  else if (parts.length == 2) {
    var wholeNum = parts[0];
    var decimal = parts[1];
    var denom = Math.pow(10, decimal.length);
    var factor = _FractionFormatterHighestCommonFactor(decimal, denom)
    return (wholeNum == '0' ? '' : (wholeNum + " ")) + (decimal / factor) + '/' + (denom / factor);
  } else {
    return "";
  }
}
hewstone
  • 4,425
  • 2
  • 23
  • 24
0

I just want a leave one alternative that I found to convert decimal numbers into fractions and reducing fractions, it's a JS library.

The library calls fraction.js, it was really helpful for me and save me a lot time and work. Hope can be useful to somebody else!

Karl
  • 1,814
  • 1
  • 25
  • 37
Jabel Márquez
  • 772
  • 1
  • 11
  • 38
0

This may be a little old but the code that was posted fails on 0 values I have fixed that error and will post the updated code below

//function to get highest common factor of two numbers (a fraction)
function HCF(u, v) { 
    var U = u, V = v
    while (true) {
        if (!(U%=V)) return V
        if (!(V%=U)) return U 
    } 
}
//convert a decimal into a fraction
function fraction(decimal){

    if(!decimal){
        decimal=this;
    }
    whole = String(decimal).split('.')[0];
    decimal = parseFloat("."+String(decimal).split('.')[1]);
    num = "1";
    for(z=0; z<String(decimal).length-2; z++){
        num += "0";
    }
    decimal = decimal*num;
    num = parseInt(num);
    for(z=2; z<decimal+1; z++){
        if(decimal%z==0 && num%z==0){
            decimal = decimal/z;
            num = num/z;
            z=2;
        }
    }
    //if format of fraction is xx/xxx
    if (decimal.toString().length == 2 && 
        num.toString().length == 3) {
            //reduce by removing trailing 0's
            // '
    decimal = Math.round(Math.round(decimal)/10);
    num = Math.round(Math.round(num)/10);
}
//if format of fraction is xx/xx
else if (decimal.toString().length == 2 && 
        num.toString().length == 2) {
    decimal = Math.round(decimal/10);
    num = Math.round(num/10);
}
//get highest common factor to simplify
var t = HCF(decimal, num);

//return the fraction after simplifying it

if(isNaN(whole) === true)
{
 whole = "0";
}

if(isNaN(decimal) === true)
{
    return ((whole==0)?"0" : whole);
}
else
{
    return ((whole==0)?"0 " : whole+" ")+decimal/t+"/"+num/t;
}
}
0

I know this is an old question, but I have created a function that has been greatly simplified.

Math.fraction=function(x){
return x?+x?x.toString().includes(".")?x.toString().replace(".","")/(function(a,b){return b?arguments.callee(b,a%b):a;})(x.toString().replace(".",""),"1"+"0".repeat(x.toString().split(".")[1].length))+"/"+("1"+"0".repeat(x.toString().split(".")[1].length))/(function(a,b){return b?arguments.callee(b,a%b):a;})(x.toString().replace(".",""),"1"+"0".repeat(x.toString().split(".")[1].length)):x+"/1":NaN:void 0;
}

Call it with Math.fraction(2.56)

It will:

  • return NaN if the input is not a number
  • return undefined if the input is undefined
  • reduce the fraction
  • return a string (use Math.fraction(2.56).split("/") for an array containing the numerator and denominator)

Please note that this uses the deprecated arguments.callee, and thus may be incompatible in some browsers.

Test it here

ayunami2000
  • 441
  • 6
  • 10
0

Intro & explanation

Although old, I see this question has had some edits recently and its still the first thing that came up on search engines for me when I was looking into this.

For that reason I wanted to share: if anyone comes to this looking for a simple (naive) solution that doesn't require pulling in a library, this is what I ended up using for my purpose.

Its a simple brute-force that first looks if 1/⌊1/x⌉ is a good approximation, if not it checks 2/⌊2/x⌉, 3/⌊3/x⌉ and so on until the first result within the given error bound.

Code

function getFrac(x, maxErr){
  let s = x<0?-1:1
  x = Math.abs(x),
  i = Math.floor(x),
  d = x-i,
  maxErr = maxErr ? maxErr : Math.pow(10,-6);
  if(d<maxErr) return [i,1];
  let n = 1,
      nmax = Math.ceil(d*Math.min(
          Math.pow(10,Math.ceil(Math.abs(Math.log10(maxErr)))),
          Number.MAX_SAFE_INTEGER
      )),
      min = Infinity,
      best = [0,0];
  while(n <= nmax){
      let err = Math.abs(d - n/Math.round(n/d));
      if(err < maxErr) return [s*(n+i*Math.round(n/d)), Math.round(n/d)];
      else if(err < min){
          min = err;
          best = [s*(n+i*Math.round(n/d)), Math.round(n/d)];
      }
      n++;
  }
  return best[1] == 0 ? false : best;
}

Example output:

getFrac(0)           // [0, 1]
getFrac(0.28)        // [7, 25]
getFrac(0.28282828)  // [28, 99]
getFrac(2.56)        // [64, 25]
getFrac(-1.33333333) // [-4, 3]
getFrac(Math.E)      // [2721, 1001]
getFrac(Math.PI)     // [355, 113]
getFrac(Math.PI, 0.01)   // [22, 7]
getFrac(Math.PI, 0.001)  // [201, 64]
getFrac(Math.PI, 10**-4) // [333, 106]
getFrac(Math.PI, 10**-12) // [4272943, 1360120]

Performance

Although this is the naive approach, it runs surprisingly well, fast enough for most needs. Both JSbench and tests in my console using performance.now() indicate getFrac takes ~4 microseconds to return on average when fed random input using the default error level of 6 decimal places. The linked JSBench was giving me a result of ~200 ops/sec on my machine when calling getFrac 1000 times each op. I include below the timer script I used in the console.

let N = 10000,
    nums = new Array(N).fill(0).map(v=>Math.random()*(Math.random()<2/3?1:100)),
    t = 0;
for(let n of nums){
  const t0 = performance.now();
  getFrac(n);
  const t1 = performance.now();
  t += t1-t0;
}
console.log((t/N)*1000,'micros'); // 4.039999999850989 micros
taylor8294
  • 76
  • 6