2

I would like to convert decimal of numbers to the nearest fraction with one number! For example, "8.75" should be "8 3/4", "1.875" should be "1 7/8" but "8,565217..." shouldn't display "8 13/23" but an error. There is a similar feature in Excel explained here

I also would like to avoid using third lib like fraction.js, and prefer using native JS/TS or Lodash! Does someone have an idea? =)

Thank you for your help!

EDIT: There is a part of the code i tried but doesn't work as expected cs 8.75 send me 35/4 and no 8 3/4...

  private checkNumberToCompute(numberToCompute: any) {
    let numberToReturn = numberToCompute;
    if (
      (!isNaN(Number(numberToCompute)) && numberToCompute.includes('.')) ||
      (numberToCompute.includes(',') && !numberToCompute.includes('/'))
    ) {
      console.log('Nombre à décimal sans fraction');
      numberToReturn = this.computeFractions(numberToCompute);
    }
    return numberToReturn;
  }
  private computeFractions(numberToCompute: any): string {
    console.log('numberToCompute', numberToCompute);

    const lenghtOfDecimals = numberToCompute.substring(numberToCompute.indexOf('.') + 1).length;
    let denominator = Math.pow(10, lenghtOfDecimals),
      numerator = numberToCompute * denominator;
    const divisor = this.gcd(numerator, denominator);

    numerator /= divisor;
    denominator /= divisor;
    return Math.floor(numerator) + '/' + Math.floor(denominator);
  }

  private gcd(numerator: number, denominator: number): any {
    if (denominator < 0.0000001) {
      return numerator;
    }
    return this.gcd(denominator, Math.floor(numerator % denominator));
  }
FloCAD
  • 113
  • 3
  • 13
  • Can you please post the code which you have tried until now in stackblitz – Anusha_Mamidala Apr 03 '19 at 12:43
  • 2
    Why should "8,565217..." display an error? Because of the comma separator? Because of the dots? Because the fraction isn't close enough to some particular set of allowed denominators? And if so, what is the set of allowed denominators, and how close is close enough? – jcalz Apr 03 '19 at 12:51
  • If you use TypeScript, you should also use ES6+, which also means you probably do not need Lodash at all. Native ES6 is more than enough for your needs. Though, if an existing library exists, it'd be preferable to use it. – Alex Beugnet Apr 03 '19 at 13:03
  • Please have a look on https://stackoverflow.com/questions/23575218/convert-decimal-number-to-fraction-in-javascript-or-closest-fraction – Sandy Genedy Apr 03 '19 at 13:23
  • @jcalz , it send an error because it has to be a simple fraction with only one digit and no numbers ! – FloCAD Apr 03 '19 at 13:27
  • 1
    Welcome to Stackoverflow. Please refer http://stackoverflow.com/help/how-to-ask . And add more details of the issue that you are facing.Share the relevant code snippets you have tried so far. – Ashish Apr 03 '19 at 13:30
  • Is it for the display or the input? – Vega Apr 03 '19 at 14:16

1 Answers1

5

Well, I'm not really sure if this is exactly what you want, but it should hopefully give you some ideas on how to proceed:

const acceptableDenominators = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const maxDistanceToNumerator = 0.0001;

function numberToFractionString(n: number): string | undefined {
    const negative = (n < 0);
    if (negative) n = -n;

    const wholePart = Math.floor(n);
    n -= wholePart;

    const denom = acceptableDenominators.find(d =>
        Math.abs(d * n - Math.round(d * n)) <= maxDistanceToNumerator
    );
    if (typeof denom === 'undefined') {
        return;
    }
    const numer = Math.round(denom * n);

    if (denom === 1) {
        return "" + (wholePart + numer) * (negative ? -1 : 1);
    }

    return (negative ? "-" : "") +
        (wholePart ? wholePart + " " : "") +
        numer + "/" + denom;

}

The idea is that you need to figure out what the acceptable denominators are for the fractions; in your case, you seem to want only one-digit numbers, so that's why I've specified just 1-9. Also you need to figure out how close the floating point number must be to the fraction to accept it. In this case, I've specified that for something to be recognized as, say, 3/5, it must be between 2.9999/5 and 3.0001/5.

Then there's a lot of edge cases to deal with (negative numbers and numbers very close to a whole number are interesting) but the main procedure is to just check each possible denominator from lowest to highest (automatically giving you a reduced fraction since it will find 1/2 before 4/8) and pick the first one where the numerator would be close enough to a whole number... or return undefined (instead of throwing an error, but you could do that if you want) if not.

Let's see if it works:

const tests = [8.75, 1.875, 8.565217, 9.99999999, -1, -0.888889,
    0, 1e140, -1e-140, -0.111111, 0.5,
    -7.66667, -7.6667, -7.667, -7.67, -7.7,
    NaN, Infinity, -Infinity];

tests.forEach(n =>
    console.log("" + n + ": " + String(numberToFractionString(n)))
);

// 8.75: 8 3/4
// 1.875: 1 7/8
// 8.565217: undefined
// 9.99999999: 10
// -1: -1
// -0.888889: -8/9
// 0: 0
// 1e+140: 1e+140
// -1e-140: 0
// -0.111111: -1/9
// 0.5: 1/2
// -7.66667: -7 2/3
// -7.6667: -7 2/3
// -7.667: undefined
// -7.67: undefined
// -7.7: undefined
// NaN: undefined
// Infinity: undefined
// -Infinity: undefined

That looks reasonable to me, although I don't know what exactly you want to see for some of those edge cases. Anyway, hope that helps. Good luck!

jcalz
  • 264,269
  • 27
  • 359
  • 360