Sercan Samet Savran
Sercan Samet Savran

Reputation: 947

Solution for Javascript toFixed and math.round wrong issue

Is there a way to proper round the number 35.855 to have two decimals in javascript? Trying to round it has different solutions based on the environment (35.85 in Chrome, 35.86 in IE9).

I was looking for a solution at which javascript does wrong rounding for the decimals (rounding .5 values down instead up) and haven't found a solution that covered all the values that I had. They behaved other in different environments / browsers.

console.log(Math.round(35.855*100)/100);
// result: 35.85 (IE9 : 35.86, Chrome: 35.85 ???)
console.log((35.855).toFixed(2));
// result: 35.85

Upvotes: 1

Views: 151

Answers (2)

Alexander Nenashev
Alexander Nenashev

Reputation: 22744

Make rounding of 1 character after the decimal part:

const round = (num, digits = 2) => {
  const str = num.toString(), idx = str.indexOf('.');
  if (idx < 0) return num;
  const left = str[idx + 1 + digits]; // round this char
  if (!left) return num;
  return parseFloat(str.slice(0, idx + 1 + digits)) + 
      (left >= 5) * (str[0] === '-' ? -1 : 1) / 10 ** digits;
}
[35.855, 35.8549999, [-35.8549999, 3], 35.895, 35.8, 35].forEach(num => 
  console.log(JSON.stringify(num), '->', round(...[].concat(num))));

And a benchmark:

Cycles: 1000000 / Chrome/117
------------------------------------------------------------
Alexander slice       151/min  1.0x  163  166  159  151  169
Sercan Samet Savran   845/min  5.6x  846  845  846  851  859
------------------------------------------------------------
https://github.com/silentmantra/benchmark

<script benchmark data-count="1000000">

// @benchmark Alexander slice
const round = (num, digits = 2) => {
  const str = num.toString();
  const idx = str.indexOf('.');
  if(idx < 0){
    return num;
  }
  const left = str[idx + 1 + digits];
  return left ? parseFloat(str.slice(0, idx + 1 + digits)) +(left >= 5) * (str[0] === '-' ? -1 : 1)/ 10 ** digits : num;
}
// @run

round(35.855);
round(35.8549999);
round(35.8549999, 3);
round(35.85);
round(35.8);
round(35);

// @benchmark Sercan Samet Savran

function roundToTwoDecimal(myNumber){
    let myStrNumber = myNumber.toString();
    // checking our number has decimals and if enough decimal points are present 
    if(myStrNumber.split(".").length > 1 && myStrNumber.split(".")[1].length > 2){
        // doubling the last decimal e.g. 35.855 is now = 35.8555 and parsing back to float  
        myStrNumber += myStrNumber.substring(myStrNumber.length-1);
        myNumber = parseFloat(myStrNumber);
    } // no additional decimal needed here.
    // returning the myNumber*100 = 3585.55 ---round--> 3586 / 100 = 35.86 
    return Math.round(myNumber*100)/100;
}

// @run

roundToTwoDecimal(35.855);
roundToTwoDecimal(35.8549999);
roundToTwoDecimal(35.8549999, 3);
roundToTwoDecimal(35.85);
roundToTwoDecimal(35.8);
roundToTwoDecimal(35);


</script>
<script src="https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js"></script>

Upvotes: 2

Sercan Samet Savran
Sercan Samet Savran

Reputation: 947

My solution is just to add the same digit at the end of the number (if there are more than 1 digit), in order to force the previous number to round properly.

function roundToTwoDecimal(myNumber){
    let myStrNumber = myNumber.toString();
    // checking our number has decimals and if enough decimal points are present 
    if(myStrNumber.split(".").length > 1 && myStrNumber.split(".")[1].length > 2){
        // doubling the last decimal e.g. 35.855 is now = 35.8555 and parsing back to float  
        myStrNumber += myStrNumber.substring(myStrNumber.length-1);
        myNumber = parseFloat(myStrNumber);
    } // no additional decimal needed here.
    // returning the myNumber*100 = 3585.55 ---round--> 3586 / 100 = 35.86 
    return Math.round(myNumber*100)/100;
}

Upvotes: 0

Related Questions