Search code examples
typescriptlodash

How to convert decimals to the nearest fraction


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));
  }

Solution

  • 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!