Ben in CA
Ben in CA

Reputation: 851

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?

Upvotes: 1

Views: 264

Answers (4)

Louys Patrice Bessette
Louys Patrice Bessette

Reputation: 33933

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.

Upvotes: 1

Frenchy
Frenchy

Reputation: 17027

pure mathematic, just for decimal number negative and positive: but .tofixed() seems to be bugged:

just fyi: for a decimal number +/-, 0.xxxxxx Math.floor(-Math.abs(Math.log(n))/Math/log(0)) gives the position of first digit non zero after dot

function formatNumber(n) {
  console.log(n + " -> " + n.toFixed(-Math.floor(Math.log(Math.abs(n)) / Math.log(10))));
  return n.toFixed(-Math.floor(Math.log(Math.abs(n)) / Math.log(10)))
}

// You test cases
formatNumber(0.0001)
formatNumber(0.2453535)
formatNumber(0.55)
formatNumber(0.055)
formatNumber(0.0055)
formatNumber(0.15)
formatNumber(0.015)
formatNumber(0.0015)
formatNumber(0.25)
formatNumber(0.025)
formatNumber(0.0025)
formatNumber(0.00412)

// Negative numbers
formatNumber(-0.0045)

// Negative numbers
//console.log(formatNumber(-2.01))
formatNumber(-0.0045)
formatNumber(-0.55)
formatNumber(-0.15)
formatNumber(-0.015)
// Non float numbers
//console.log(formatNumber(4))
//console.log(formatNumber(102))

// The 0.011 case mentionned in comments
formatNumber(0.0105)

result:
0.0001 -> 0.0001
0.2453535 -> 0.2
0.55 -> 0.6
0.055 -> 0.06
0.0055 -> 0.005   bugged
0.15 -> 0.1       bugged
0.015 -> 0.01     bugged
0.0015 -> 0.002
0.25 -> 0.3
0.025 -> 0.03
0.0025 -> 0.003
0.00412 -> 0.004
-0.0045 -> -0.004
-0.0045 -> -0.004
-0.55 -> -0.6     bugged
-0.15 -> -0.1 
-0.015 -> -0.01
0.0105 -> 0.01

so like toFixed() is bugged, another solution with regex:

i use this regex to trap the number: /\.(0*)([^0])([0-9])\d*/

function formatNumber(n) {

  v = n.toString().replace(/\.(0*)([^0])([0-9])\d*/, (g1, g2, g3, g4) => {
    return "." + g2 + (+g4 < 5 ? +g3 : +g3 + 1);
  });
  return +v;
}

Explanation:
g1 is the full match string

\. dot not selected following by

(0*) g2 = "" or lot "0" following by

([^0]) g3 = one digit but not 0 following by

([0-9]) g4 = one digit following by

\d* any digit not selected which finish the number

The full solution for any number:

function formatNumber(n) {
  v = n.toString().replace(/\.(0*)([^0])([0-9])\d*/, (g1, g2, g3, g4) => {
    return "." + g2 + (+g4 < 5 ? +g3 : +g3 + 1);
  });
  return +v;
}
test=[0.0001, 0.2453535 , 0.2553535, 
0.5, 0.055, 0.0055, 0.15, 0.015, 0.0015,
0.25, 0.025, 0.0025 , 0.00412, -0.0045, 
-2.01, -0.016 , -0.012, -0.0045, -0.055, 
-0.15, -0.015, -4, 102, 0.0105, 2.00015, -4.026];

resultwaiting=[0.0001, 0.2 , 0.3, 
0.5, 0.06, 0.006, 0.2, 0.02, 0.002,
0.3, 0.03, 0.003 , 0.004, -0.005, 
-2.01, -0.02 , -0.01, -0.005, -0.06, 
-0.2, -0.02, -4, 102, 0.01, 2.0002, -4.03];

var i =0;
test.forEach(x => console.log(x, formatNumber(x), formatNumber(x) == resultwaiting[i++] ))

Upvotes: 0

fdomn-m
fdomn-m

Reputation: 28621

You can use

val.match("\.0+")[0].length

to determine exactly how many 0's without including any after the precision. Add a check to ensure ".0" appears (otherwise nothing to do) gives:

function toPrecision(val) {
   return (val+"").indexOf(".0")>=0 ? val.toFixed((val+"").match("\.0+")[0].length) : val;
}

console.log(toPrecision(123))

console.log(toPrecision(0.1))
console.log(toPrecision(0.1455))

console.log(toPrecision(0.02))
console.log(toPrecision(0.02455))
console.log(toPrecision(0.0255))

console.log(toPrecision(0.00412))
console.log(toPrecision(0.004102))

Upvotes: 0

epascarello
epascarello

Reputation: 207527

Using a reg exp is one way of handling it. It is checking for numbers less than one since that is the only requirements given.

const weirdRound = num => {
  return +(+num).toString().replace(/0\.(0+)?([1-9])(\d)\d*/, (_, z, n, r) => {
    if (+r > 4) n = +n + 1;
    return `0.${z ? z : ''}${n}`;
  });
}

[0.21, 0.25, 0.021, 0.025, 0.001, 0.00111111].forEach(x => console.log(x, weirdRound(x)));

Upvotes: 2

Related Questions