[javascript] Formatting a number with exactly two decimals in JavaScript

I have this line of code which rounds my numbers to two decimal places. But I get numbers like this: 10.8, 2.4, etc. These are not my idea of two decimal places so how I can improve the following?

Math.round(price*Math.pow(10,2))/Math.pow(10,2);

I want numbers like 10.80, 2.40, etc. Use of jQuery is fine with me.

This question is related to javascript rounding decimal-point

The answer is


Here's a TypeScript implementation of https://stackoverflow.com/a/21323330/916734. It also dries things up with functions, and allows for a optional digit offset.

export function round(rawValue: number | string, precision = 0, fractionDigitOffset = 0): number | string {
  const value = Number(rawValue);
  if (isNaN(value)) return rawValue;

  precision = Number(precision);
  if (precision % 1 !== 0) return NaN;

  let [ stringValue, exponent ] = scientificNotationToParts(value);

  let shiftExponent = exponentForPrecision(exponent, precision, Shift.Right);
  const enlargedValue = toScientificNotation(stringValue, shiftExponent);
  const roundedValue = Math.round(enlargedValue);

  [ stringValue, exponent ] = scientificNotationToParts(roundedValue);
  const precisionWithOffset = precision + fractionDigitOffset;
  shiftExponent = exponentForPrecision(exponent, precisionWithOffset, Shift.Left);

  return toScientificNotation(stringValue, shiftExponent);
}

enum Shift {
  Left = -1,
  Right = 1,
}

function scientificNotationToParts(value: number): Array<string> {
  const [ stringValue, exponent ] = value.toString().split('e');
  return [ stringValue, exponent ];
}

function exponentForPrecision(exponent: string, precision: number, shift: Shift): number {
  precision = shift * precision;
  return exponent ? (Number(exponent) + precision) : precision;
}

function toScientificNotation(value: string, exponent: number): number {
  return Number(`${value}e${exponent}`);
}

I'm fix the problem the modifier. Support 2 decimal only.

_x000D_
_x000D_
$(function(){_x000D_
  //input number only._x000D_
  convertNumberFloatZero(22); // output : 22.00_x000D_
  convertNumberFloatZero(22.5); // output : 22.50_x000D_
  convertNumberFloatZero(22.55); // output : 22.55_x000D_
  convertNumberFloatZero(22.556); // output : 22.56_x000D_
  convertNumberFloatZero(22.555); // output : 22.55_x000D_
  convertNumberFloatZero(22.5541); // output : 22.54_x000D_
  convertNumberFloatZero(22222.5541); // output : 22,222.54_x000D_
_x000D_
  function convertNumberFloatZero(number){_x000D_
 if(!$.isNumeric(number)){_x000D_
  return 'NaN';_x000D_
 }_x000D_
 var numberFloat = number.toFixed(3);_x000D_
 var splitNumber = numberFloat.split(".");_x000D_
 var cNumberFloat = number.toFixed(2);_x000D_
 var cNsplitNumber = cNumberFloat.split(".");_x000D_
 var lastChar = splitNumber[1].substr(splitNumber[1].length - 1);_x000D_
 if(lastChar > 0 && lastChar < 5){_x000D_
  cNsplitNumber[1]--;_x000D_
 }_x000D_
 return Number(splitNumber[0]).toLocaleString('en').concat('.').concat(cNsplitNumber[1]);_x000D_
  };_x000D_
});
_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
_x000D_
_x000D_
_x000D_


Here's a simple one

function roundFloat(num,dec){
    var d = 1;
    for (var i=0; i<dec; i++){
        d += "0";
    }
    return Math.round(num * d) / d;
}

Use like alert(roundFloat(1.79209243929,4));

Jsfiddle


@heridev and I created a small function in jQuery.

You can try next:

HTML

<input type="text" name="one" class="two-digits"><br>
<input type="text" name="two" class="two-digits">?

jQuery

// apply the two-digits behaviour to elements with 'two-digits' as their class
$( function() {
    $('.two-digits').keyup(function(){
        if($(this).val().indexOf('.')!=-1){         
            if($(this).val().split(".")[1].length > 2){                
                if( isNaN( parseFloat( this.value ) ) ) return;
                this.value = parseFloat(this.value).toFixed(2);
            }  
         }            
         return this; //for chaining
    });
});

? DEMO ONLINE:

http://jsfiddle.net/c4Wqn/


This is very simple and works just as well as any of the others:

function parseNumber(val, decimalPlaces) {
    if (decimalPlaces == null) decimalPlaces = 0
    var ret = Number(val).toFixed(decimalPlaces)
    return Number(ret)
}

Since toFixed() can only be called on numbers, and unfortunately returns a string, this does all the parsing for you in both directions. You can pass a string or a number, and you get a number back every time! Calling parseNumber(1.49) will give you 1, and parseNumber(1.49,2) will give you 1.50. Just like the best of 'em!


Number(((Math.random() * 100) + 1).toFixed(2))

this will return a random number from 1 to 100 rounded to 2 decimal places.


Round down

function round_down(value, decPlaces) {
    return Math.floor(value * Math.pow(10, decPlaces)) / Math.pow(10, decPlaces);
}

Round up

function round_up(value, decPlaces) {
    return Math.ceil(value * Math.pow(10, decPlaces)) / Math.pow(10, decPlaces);
}

Round nearest

function round_nearest(value, decPlaces) {
    return Math.round(value * Math.pow(10, decPlaces)) / Math.pow(10, decPlaces);
}

Merged https://stackoverflow.com/a/7641824/1889449 and https://www.kirupa.com/html5/rounding_numbers_in_javascript.htm Thanks them.


I didn't find an accurate solution for this problem, so I created my own:

function inprecise_round(value, decPlaces) {
  return Math.round(value*Math.pow(10,decPlaces))/Math.pow(10,decPlaces);
}

function precise_round(value, decPlaces){
    var val = value * Math.pow(10, decPlaces);
    var fraction = (Math.round((val-parseInt(val))*10)/10);

    //this line is for consistency with .NET Decimal.Round behavior
    // -342.055 => -342.06
    if(fraction == -0.5) fraction = -0.6;

    val = Math.round(parseInt(val) + fraction) / Math.pow(10, decPlaces);
    return val;
}

Examples:

_x000D_
_x000D_
function inprecise_round(value, decPlaces) {_x000D_
  return Math.round(value * Math.pow(10, decPlaces)) / Math.pow(10, decPlaces);_x000D_
}_x000D_
_x000D_
function precise_round(value, decPlaces) {_x000D_
  var val = value * Math.pow(10, decPlaces);_x000D_
  var fraction = (Math.round((val - parseInt(val)) * 10) / 10);_x000D_
_x000D_
  //this line is for consistency with .NET Decimal.Round behavior_x000D_
  // -342.055 => -342.06_x000D_
  if (fraction == -0.5) fraction = -0.6;_x000D_
_x000D_
  val = Math.round(parseInt(val) + fraction) / Math.pow(10, decPlaces);_x000D_
  return val;_x000D_
}_x000D_
_x000D_
// This may produce different results depending on the browser environment_x000D_
console.log("342.055.toFixed(2)         :", 342.055.toFixed(2)); // 342.06 on Chrome & IE10_x000D_
_x000D_
console.log("inprecise_round(342.055, 2):", inprecise_round(342.055, 2)); // 342.05_x000D_
console.log("precise_round(342.055, 2)  :", precise_round(342.055, 2));   // 342.06_x000D_
console.log("precise_round(-342.055, 2) :", precise_round(-342.055, 2));  // -342.06_x000D_
_x000D_
console.log("inprecise_round(0.565, 2)  :", inprecise_round(0.565, 2));   // 0.56_x000D_
console.log("precise_round(0.565, 2)    :", precise_round(0.565, 2));     // 0.57
_x000D_
_x000D_
_x000D_


(Math.round((10.2)*100)/100).toFixed(2)

That should yield: 10.20

(Math.round((.05)*100)/100).toFixed(2)

That should yield: 0.05

(Math.round((4.04)*100)/100).toFixed(2)

That should yield: 4.04

etc.


toFixed(n) provides n length after the decimal point; toPrecision(x) provides x total length.

Use this method below

// Example: toPrecision(4) when the number has 7 digits (3 before, 4 after)
    // It will round to the tenths place
    num = 500.2349;
    result = num.toPrecision(4); // result will equal 500.2

AND if you want the number to be fixed use

result = num.toFixed(2);

Here is my 1-line solution: Number((yourNumericValueHere).toFixed(2));

Here's what happens:

1) First, you apply .toFixed(2) onto the number that you want to round off the decimal places of. Note that this will convert the value to a string from number. So if you are using Typescript, it will throw an error like this:

"Type 'string' is not assignable to type 'number'"

2) To get back the numeric value or to convert the string to numeric value, simply apply the Number() function on that so-called 'string' value.

For clarification, look at the example below:

EXAMPLE: I have an amount that has upto 5 digits in the decimal places and I would like to shorten it to upto 2 decimal places. I do it like so:

_x000D_
_x000D_
var price = 0.26453;_x000D_
var priceRounded = Number((price).toFixed(2));_x000D_
console.log('Original Price: ' + price);_x000D_
console.log('Price Rounded: ' + priceRounded);
_x000D_
_x000D_
_x000D_


With these examples you will still get an error when trying to round the number 1.005 the solution is to either use a library like Math.js or this function:

function round(value: number, decimals: number) {
    return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
}

Using this response by reference: https://stackoverflow.com/a/21029698/454827

I build a function to get dynamic numbers of decimals:

function toDec(num, dec)
{
        if(typeof dec=='undefined' || dec<0)
                dec = 2;

        var tmp = dec + 1;
        for(var i=1; i<=tmp; i++)
                num = num * 10;

        num = num / 10;
        num = Math.round(num);
        for(var i=1; i<=dec; i++)
                num = num / 10;

        num = num.toFixed(dec);

        return num;
}

here working example: https://jsfiddle.net/wpxLduLc/


Round your decimal value, then use toFixed(x) for your expected digit(s).

function parseDecimalRoundAndFixed(num,dec){
  var d =  Math.pow(10,dec);
  return (Math.round(num * d) / d).toFixed(dec);
}

Call

parseDecimalRoundAndFixed(10.800243929,4) => 10.80 parseDecimalRoundAndFixed(10.807243929,2) => 10.81


_x000D_
_x000D_
parse = function (data) {_x000D_
       data = Math.round(data*Math.pow(10,2))/Math.pow(10,2);_x000D_
       if (data != null) {_x000D_
            var lastone = data.toString().split('').pop();_x000D_
            if (lastone != '.') {_x000D_
                 data = parseFloat(data);_x000D_
            }_x000D_
       }_x000D_
       return data;_x000D_
  };_x000D_
_x000D_
$('#result').html(parse(200)); // output 200_x000D_
$('#result1').html(parse(200.1)); // output 200.1_x000D_
$('#result2').html(parse(200.10)); // output 200.1_x000D_
$('#result3').html(parse(200.109)); // output 200.11
_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>_x000D_
<div id="result"></div>_x000D_
<div id="result1"></div>_x000D_
<div id="result2"></div>_x000D_
<div id="result3"></div>
_x000D_
_x000D_
_x000D_


_x000D_
_x000D_
/*Due to all told stuff. You may do 2 things for different purposes:_x000D_
When showing/printing stuff use this in your alert/innerHtml= contents:_x000D_
YourRebelNumber.toFixed(2)*/_x000D_
_x000D_
var aNumber=9242.16;_x000D_
var YourRebelNumber=aNumber-9000;_x000D_
alert(YourRebelNumber);_x000D_
alert(YourRebelNumber.toFixed(2));_x000D_
_x000D_
/*and when comparing use:_x000D_
Number(YourRebelNumber.toFixed(2))*/_x000D_
_x000D_
if(YourRebelNumber==242.16)alert("Not Rounded");_x000D_
if(Number(YourRebelNumber.toFixed(2))==242.16)alert("Rounded");_x000D_
_x000D_
/*Number will behave as you want in that moment. After that, it'll return to its defiance._x000D_
*/
_x000D_
_x000D_
_x000D_


I found a very simple way that solved this problem for me and can be used or adapted:

td[row].innerHTML = price.toPrecision(price.toFixed(decimals).length

100% working!!! Try it

_x000D_
_x000D_
<html>_x000D_
     <head>_x000D_
      <script>_x000D_
      function replacePonto(){_x000D_
        var input = document.getElementById('qtd');_x000D_
        var ponto = input.value.split('.').length;_x000D_
        var slash = input.value.split('-').length;_x000D_
        if (ponto > 2)_x000D_
                input.value=input.value.substr(0,(input.value.length)-1);_x000D_
_x000D_
        if(slash > 2)_x000D_
                input.value=input.value.substr(0,(input.value.length)-1);_x000D_
_x000D_
        input.value=input.value.replace(/[^0-9.-]/,'');_x000D_
_x000D_
        if (ponto ==2)_x000D_
 input.value=input.value.substr(0,(input.value.indexOf('.')+3));_x000D_
_x000D_
if(input.value == '.')_x000D_
 input.value = "";_x000D_
              }_x000D_
      </script>_x000D_
      </head>_x000D_
      <body>_x000D_
         <input type="text" id="qtd" maxlength="10" style="width:140px" onkeyup="return replacePonto()">_x000D_
      </body>_x000D_
    </html>
_x000D_
_x000D_
_x000D_


This is an old topic but still top-ranked Google results and the solutions offered share the same floating point decimals issue. Here is the (very generic) function I use, thanks to MDN:

function round(value, exp) {
  if (typeof exp === 'undefined' || +exp === 0)
    return Math.round(value);

  value = +value;
  exp = +exp;

  if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0))
    return NaN;

  // Shift
  value = value.toString().split('e');
  value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)));

  // Shift back
  value = value.toString().split('e');
  return +(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp));
}

As we can see, we don't get these issues:

round(1.275, 2);   // Returns 1.28
round(1.27499, 2); // Returns 1.27

This genericity also provides some cool stuff:

round(1234.5678, -2);   // Returns 1200
round(1.2345678e+2, 2); // Returns 123.46
round("123.45");        // Returns 123

Now, to answer the OP's question, one has to type:

round(10.8034, 2).toFixed(2); // Returns "10.80"
round(10.8, 2).toFixed(2);    // Returns "10.80"

Or, for a more concise, less generic function:

function round2Fixed(value) {
  value = +value;

  if (isNaN(value))
    return NaN;

  // Shift
  value = value.toString().split('e');
  value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] + 2) : 2)));

  // Shift back
  value = value.toString().split('e');
  return (+(value[0] + 'e' + (value[1] ? (+value[1] - 2) : -2))).toFixed(2);
}

You can call it with:

round2Fixed(10.8034); // Returns "10.80"
round2Fixed(10.8);    // Returns "10.80"

Various examples and tests (thanks to @t-j-crowder!):

_x000D_
_x000D_
function round(value, exp) {_x000D_
  if (typeof exp === 'undefined' || +exp === 0)_x000D_
    return Math.round(value);_x000D_
_x000D_
  value = +value;_x000D_
  exp = +exp;_x000D_
_x000D_
  if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0))_x000D_
    return NaN;_x000D_
_x000D_
  // Shift_x000D_
  value = value.toString().split('e');_x000D_
  value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)));_x000D_
_x000D_
  // Shift back_x000D_
  value = value.toString().split('e');_x000D_
  return +(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp));_x000D_
}_x000D_
function naive(value, exp) {_x000D_
  if (!exp) {_x000D_
    return Math.round(value);_x000D_
  }_x000D_
  var pow = Math.pow(10, exp);_x000D_
  return Math.round(value * pow) / pow;_x000D_
}_x000D_
function test(val, places) {_x000D_
  subtest(val, places);_x000D_
  val = typeof val === "string" ? "-" + val : -val;_x000D_
  subtest(val, places);_x000D_
}_x000D_
function subtest(val, places) {_x000D_
  var placesOrZero = places || 0;_x000D_
  var naiveResult = naive(val, places);_x000D_
  var roundResult = round(val, places);_x000D_
  if (placesOrZero >= 0) {_x000D_
    naiveResult = naiveResult.toFixed(placesOrZero);_x000D_
    roundResult = roundResult.toFixed(placesOrZero);_x000D_
  } else {_x000D_
    naiveResult = naiveResult.toString();_x000D_
    roundResult = roundResult.toString();_x000D_
  }_x000D_
  $("<tr>")_x000D_
    .append($("<td>").text(JSON.stringify(val)))_x000D_
    .append($("<td>").text(placesOrZero))_x000D_
    .append($("<td>").text(naiveResult))_x000D_
    .append($("<td>").text(roundResult))_x000D_
    .appendTo("#results");_x000D_
}_x000D_
test(0.565, 2);_x000D_
test(0.575, 2);_x000D_
test(0.585, 2);_x000D_
test(1.275, 2);_x000D_
test(1.27499, 2);_x000D_
test(1234.5678, -2);_x000D_
test(1.2345678e+2, 2);_x000D_
test("123.45");_x000D_
test(10.8034, 2);_x000D_
test(10.8, 2);_x000D_
test(1.005, 2);_x000D_
test(1.0005, 2);
_x000D_
table {_x000D_
  border-collapse: collapse;_x000D_
}_x000D_
table, td, th {_x000D_
  border: 1px solid #ddd;_x000D_
}_x000D_
td, th {_x000D_
  padding: 4px;_x000D_
}_x000D_
th {_x000D_
  font-weight: normal;_x000D_
  font-family: sans-serif;_x000D_
}_x000D_
td {_x000D_
  font-family: monospace;_x000D_
}
_x000D_
<table>_x000D_
  <thead>_x000D_
    <tr>_x000D_
      <th>Input</th>_x000D_
      <th>Places</th>_x000D_
      <th>Naive</th>_x000D_
      <th>Thorough</th>_x000D_
    </tr>_x000D_
  </thead>_x000D_
  <tbody id="results">_x000D_
  </tbody>_x000D_
</table>_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
_x000D_
_x000D_
_x000D_


You could also use the .toPrecision() method and some custom code, and always round up to the nth decimal digit regardless the length of int part.

function glbfrmt (number, decimals, seperator) {
    return typeof number !== 'number' ? number : number.toPrecision( number.toString().split(seperator)[0].length + decimals);
}

You could also make it a plugin for a better use.


I got some ideas from this post a few months back, but none of the answers here, nor answers from other posts/blogs could handle all the scenarios (e.g. negative numbers and some "lucky numbers" our tester found). In the end, our tester did not find any problem with this method below. Pasting a snippet of my code:

fixPrecision: function (value) {
    var me = this,
        nan = isNaN(value),
        precision = me.decimalPrecision;

    if (nan || !value) {
        return nan ? '' : value;
    } else if (!me.allowDecimals || precision <= 0) {
        precision = 0;
    }

    //[1]
    //return parseFloat(Ext.Number.toFixed(parseFloat(value), precision));
    precision = precision || 0;
    var negMultiplier = value < 0 ? -1 : 1;

    //[2]
    var numWithExp = parseFloat(value + "e" + precision);
    var roundedNum = parseFloat(Math.round(Math.abs(numWithExp)) + 'e-' + precision) * negMultiplier;
    return parseFloat(roundedNum.toFixed(precision));
},

I also have code comments (sorry i forgot all the details already)...I'm posting my answer here for future reference:

9.995 * 100 = 999.4999999999999
Whereas 9.995e2 = 999.5
This discrepancy causes Math.round(9.995 * 100) = 999 instead of 1000.
Use e notation instead of multiplying /dividing by Math.Pow(10,precision).

Put the following in some global scope:

Number.prototype.getDecimals = function ( decDigCount ) {
   return this.toFixed(decDigCount);
}

and then try:

var a = 56.23232323;
a.getDecimals(2); // will return 56.23

Update

Note that toFixed() can only work for the number of decimals between 0-20 i.e. a.getDecimals(25) may generate a javascript error, so to accomodate that you may add some additional check i.e.

Number.prototype.getDecimals = function ( decDigCount ) {
   return ( decDigCount > 20 ) ? this : this.toFixed(decDigCount);
}

One way to be 100% sure that you get a number with 2 decimals:

(Math.round(num*100)/100).toFixed(2)

If this causes rounding errors, you can use the following as James has explained in his comment:

(Math.round((num * 1000)/10)/100).toFixed(2)

I usually add this to my personal library, and after some suggestions and using the @TIMINeutron solution too, and making it adaptable for decimal length then, this one fits best:

function precise_round(num, decimals) {
   var t = Math.pow(10, decimals);   
   return (Math.round((num * t) + (decimals>0?1:0)*(Math.sign(num) * (10 / Math.pow(100, decimals)))) / t).toFixed(decimals);
}

will work for the exceptions reported.


FAST AND EASY

parseFloat(number.toFixed(2))

Example

let number = 2.55435930

let roundedString = number.toFixed(2)    // "2.55"

let twoDecimalsNumber = parseFloat(roundedString)    // 2.55

let directly = parseFloat(number.toFixed(2))    // 2.55

Number(Math.round(1.005+'e2')+'e-2'); // 1.01

This worked for me: Rounding Decimals in JavaScript


The trouble with floating point values is that they are trying to represent an infinite amount of (continuous) values with a fixed amount of bits. So naturally, there must be some loss in play, and you're going to be bitten with some values.

When a computer stores 1.275 as a floating point value, it won't actually remember whether it was 1.275 or 1.27499999999999993, or even 1.27500000000000002. These values should give different results after rounding to two decimals, but they won't, since for computer they look exactly the same after storing as floating point values, and there's no way to restore the lost data. Any further calculations will only accumulate such imprecision.

So, if precision matters, you have to avoid floating point values from the start. The simplest options are to

  • use a devoted library
  • use strings for storing and passing around the values (accompanied by string operations)
  • use integers (e.g. you could be passing around the amount of hundredths of your actual value, e.g. amount in cents instead of amount in dollars)

For example, when using integers to store the number of hundredths, the function for finding the actual value is quite simple:

function descale(num, decimals) {
    var hasMinus = num < 0;
    var numString = Math.abs(num).toString();
    var precedingZeroes = '';
    for (var i = numString.length; i <= decimals; i++) {
        precedingZeroes += '0';
    }
    numString = precedingZeroes + numString;
    return (hasMinus ? '-' : '') 
        + numString.substr(0, numString.length-decimals) 
        + '.' 
        + numString.substr(numString.length-decimals);
}

alert(descale(127, 2));

With strings, you'll need rounding, but it's still manageable:

function precise_round(num, decimals) {
    var parts = num.split('.');
    var hasMinus = parts.length > 0 && parts[0].length > 0 && parts[0].charAt(0) == '-';
    var integralPart = parts.length == 0 ? '0' : (hasMinus ? parts[0].substr(1) : parts[0]);
    var decimalPart = parts.length > 1 ? parts[1] : '';
    if (decimalPart.length > decimals) {
        var roundOffNumber = decimalPart.charAt(decimals);
        decimalPart = decimalPart.substr(0, decimals);
        if ('56789'.indexOf(roundOffNumber) > -1) {
            var numbers = integralPart + decimalPart;
            var i = numbers.length;
            var trailingZeroes = '';
            var justOneAndTrailingZeroes = true;
            do {
                i--;
                var roundedNumber = '1234567890'.charAt(parseInt(numbers.charAt(i)));
                if (roundedNumber === '0') {
                    trailingZeroes += '0';
                } else {
                    numbers = numbers.substr(0, i) + roundedNumber + trailingZeroes;
                    justOneAndTrailingZeroes = false;
                    break;
                }
            } while (i > 0);
            if (justOneAndTrailingZeroes) {
                numbers = '1' + trailingZeroes;
            }
            integralPart = numbers.substr(0, numbers.length - decimals);
            decimalPart = numbers.substr(numbers.length - decimals);
        }
    } else {
        for (var i = decimalPart.length; i < decimals; i++) {
            decimalPart += '0';
        }
    }
    return (hasMinus ? '-' : '') + integralPart + (decimals > 0 ? '.' + decimalPart : '');
}

alert(precise_round('1.275', 2));
alert(precise_round('1.27499999999999993', 2));

Note that this function rounds to nearest, ties away from zero, while IEEE 754 recommends rounding to nearest, ties to even as the default behavior for floating point operations. Such modifications are left as an exercise for the reader :)


In general, decimal rounding is done by scaling: round(num * p) / p

Naive implementation

Using the following function with halfway numbers, you will get either the upper rounded value as expected, or the lower rounded value sometimes depending on the input.

This inconsistency in rounding may introduce hard to detect bugs in the client code.

_x000D_
_x000D_
function naiveRound(num, decimalPlaces) {
    var p = Math.pow(10, decimalPlaces);
    return Math.round(num * p) / p;
}

console.log( naiveRound(1.245, 2) );  // 1.25 correct (rounded as expected)
console.log( naiveRound(1.255, 2) );  // 1.25 incorrect (should be 1.26)
_x000D_
_x000D_
_x000D_

Better implementations

By converting the number to a string in the exponential notation, positive numbers are rounded as expected. But, be aware that negative numbers round differently than positive numbers.

In fact, it performs what is basically equivalent to "round half up" as the rule, you will see that round(-1.005, 2) evaluates to -1 even though round(1.005, 2) evaluates to 1.01. The lodash _.round method uses this technique.

_x000D_
_x000D_
/**
 * Round half up ('round half towards positive infinity')
 * Uses exponential notation to avoid floating-point issues.
 * Negative numbers round differently than positive numbers.
 */
function round(num, decimalPlaces) {
    num = Math.round(num + "e" + decimalPlaces);
    return Number(num + "e" + -decimalPlaces);
}

// test rounding of half
console.log( round(0.5, 0) );  // 1
console.log( round(-0.5, 0) ); // 0

// testing edge cases
console.log( round(1.005, 2) );   // 1.01
console.log( round(2.175, 2) );   // 2.18
console.log( round(5.015, 2) );   // 5.02

console.log( round(-1.005, 2) );  // -1
console.log( round(-2.175, 2) );  // -2.17
console.log( round(-5.015, 2) );  // -5.01
_x000D_
_x000D_
_x000D_

If you want the usual behavior when rounding negative numbers, you would need to convert negative numbers to positive before calling Math.round(), and then convert them back to negative numbers before returning.

// Round half away from zero
function round(num, decimalPlaces) {
    num = Math.round(Math.abs(num) + "e" + decimalPlaces) * Math.sign(num);
    return Number(num + "e" + -decimalPlaces);
}

There is a different purely mathematical technique to perform round-to-nearest (using "round half away from zero"), in which epsilon correction is applied before calling the rounding function.

Simply, we add the smallest possible float value (= 1.0 ulp; unit in the last place) to the number before rounding. This moves to the next representable value after the number, away from zero.

_x000D_
_x000D_
/**
 * Round half away from zero ('commercial' rounding)
 * Uses correction to offset floating-point inaccuracies.
 * Works symmetrically for positive and negative numbers.
 */
function round(num, decimalPlaces) {
    var p = Math.pow(10, decimalPlaces);
    var e = Number.EPSILON * num * p;
    return Math.round((num * p) + e) / p;
}

// test rounding of half
console.log( round(0.5, 0) );  // 1
console.log( round(-0.5, 0) ); // -1

// testing edge cases
console.log( round(1.005, 2) );  // 1.01
console.log( round(2.175, 2) );  // 2.18
console.log( round(5.015, 2) );  // 5.02

console.log( round(-1.005, 2) ); // -1.01
console.log( round(-2.175, 2) ); // -2.18
console.log( round(-5.015, 2) ); // -5.02
_x000D_
_x000D_
_x000D_

This is needed to offset the implicit round-off error that may occur during encoding of decimal numbers, particularly those having "5" in the last decimal position, like 1.005, 2.675 and 16.235. Actually, 1.005 in decimal system is encoded to 1.0049999999999999 in 64-bit binary float; while, 1234567.005 in decimal system is encoded to 1234567.0049999998882413 in 64-bit binary float.

It is worth noting that the maximum binary round-off error is dependent upon (1) the magnitude of the number and (2) the relative machine epsilon (2^-52).


I don't know why can't I add a comment to a previous answer (maybe I'm hopelessly blind, I don't know), but I came up with a solution using @Miguel's answer:

function precise_round(num,decimals) {
   return Math.round(num*Math.pow(10, decimals)) / Math.pow(10, decimals);
}

And its two comments (from @bighostkim and @Imre):

  • Problem with precise_round(1.275,2) not returning 1.28
  • Problem with precise_round(6,2) not returning 6.00 (as he wanted).

My final solution is as follows:

function precise_round(num,decimals) {
    var sign = num >= 0 ? 1 : -1;
    return (Math.round((num*Math.pow(10,decimals)) + (sign*0.001)) / Math.pow(10,decimals)).toFixed(decimals);
}

As you can see I had to add a little bit of "correction" (it's not what it is, but since Math.round is lossy - you can check it on jsfiddle.net - this is the only way I knew how to "fix" it). It adds 0.001 to the already padded number, so it is adding a 1 three 0s to the right of the decimal value. So it should be safe to use.

After that I added .toFixed(decimal) to always output the number in the correct format (with the right amount of decimals).

So that's pretty much it. Use it well ;)

EDIT: added functionality to the "correction" of negative numbers.


Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to rounding

How to round a numpy array? How to pad a string with leading zeros in Python 3 Python - round up to the nearest ten How to round a Double to the nearest Int in swift? Using Math.round to round to one decimal place? How to round to 2 decimals with Python? Rounding to 2 decimal places in SQL Rounding to two decimal places in Python 2.7? Round a floating-point number down to the nearest integer? Rounding BigDecimal to *always* have two decimal places

Examples related to decimal-point

Round to at most 2 decimal places (only if necessary) Int to Decimal Conversion - Insert decimal point at specified location Integer.valueOf() vs. Integer.parseInt() How to change the decimal separator of DecimalFormat from comma to dot/point? Decimal separator comma (',') with numberDecimal inputType in EditText How to print a float with 2 decimal places in Java? Javascript: formatting a rounded number to N decimals Formatting a number with exactly two decimals in JavaScript Leave only two decimal places after the dot In jQuery, what's the best way of formatting a number to 2 decimal places?