javascriptjqueryprecisionsignificant-digits

Remove digits representing irrelevant precision from a number with JavaScript


I'm looking for a function in Javascript (or JQuery) to remove digits representing irrelevant precision from a number. I'm calculating an estimated probability, and it could be a range of values - and the exact decimal isn't very relevant - just the first non-zero decimal from the left.

Any thoughts on a good way to accomplish that?


Solution

  • That one is not as simple as it looks.

    I assumed it has to also work on non-float and negative numbers. There also are the zero and the "Not a Number" cases.

    Here is a robust solution.

    function formatNumber(n) {
      
      // in case of a string... ParseFloat it
      n = parseFloat(n)
      
      // Fool-proof case where n is "Not A Number"
      if(isNaN(n)){return null}
    
      // Negative number detection
      let N = Math.abs(n);
      let isNegative = N !== n;
      
      // The zero case
      if(N===0){
        return n
      }
      
      // Numbers which do not need much processing
      if(N>1){return +(n.toFixed(1))}
      
      // Lets process numbers by moving the decimal dot to the right
      // until the number is more than 1
      let i = 0;
      while (Math.floor(N) < 1) {
        let dotPos = (""+N).indexOf(".") + 1
        N = (""+N).replace(".","")
        N = parseFloat(N.slice(0,dotPos)+"."+N.slice(dotPos))
        i++;
      }
    
      // Re-add the negative sign
      if (isNegative) {
        N = -N;
      }
      
      // Now round and reposition the decimal dot
      return Math.round(N) / Math.pow(10, i);
    }
    
    // ============================================================== Test cases
    let testCases = [
      { in: 0.0001, out: 0.0001 },
      { in: 0.2453535, out: 0.2 },
      { in: 0.55, out: 0.6 },
      { in: 0.055, out: 0.06 },
      { in: 0.0055, out: 0.006 },
      { in: 0.15, out: 0.2 },
      { in: 0.015, out: 0.02 },
      { in: 0.0015, out: 0.002 },
      { in: 0.25, out: 0.3 },
      { in: 0.025, out: 0.03 },
      { in: 0.0025, out: 0.003 },
      { in: 0.00412, out: 0.004 },
      { in: -0.0045, out: -0.004 },
      { in: -0.55, out: -0.5 },
      { in: -0.15, out: -0.1 },
      { in: -0.015, out: -0.01 },
      { in: -0.0105, out: -0.01 },
      { in: -0.010504, out: -0.01 },
      { in: 2, out: 2 },
      { in: 2.01, out: 2.0 },
      { in: 2.34567, out: 2.3 },
      { in: 0, out: 0 },
      { in: "0.012%", out: 0.01 },
      { in: "Hello", out: null }
    ];
    
    testCases.forEach((item) => {
      let res = formatNumber(item.in);
      let consoleMgs = `Test case: ${JSON.stringify(item)}\n Result: ${res}\n ${(res == item.out)?"PASS":"FAIL"}`
      if (res == item.out) {
        console.log(consoleMgs);
      } else {
        console.error(consoleMgs);
      }
    });


    Notice that I "moved" the decimal dot using a string manipulation instead of multiplying by 10. That is because of this case:

    //N = N * 10;
    
    console.log(0.55*10)
    console.log(0.055*10)
    console.log(0.0055*10)
    console.log(0.00055*10)

    And that is due to the fact that the multiplication is done with binary numbers, internally.

    More details here.