Related
I'd like to round at most two decimal places, but only if necessary.
Input:
10
1.7777777
9.1
Output:
10
1.78
9.1
How can I do this in JavaScript?
Use Math.round() :
Math.round(num * 100) / 100
Or to be more specific and to ensure things like 1.005 round correctly, use Number.EPSILON :
Math.round((num + Number.EPSILON) * 100) / 100
If the value is a text type:
parseFloat("123.456").toFixed(2);
If the value is a number:
var numb = 123.23454;
numb = numb.toFixed(2);
There is a downside that values like 1.5 will give "1.50" as the output. A fix suggested by #minitech:
var numb = 1.5;
numb = +numb.toFixed(2);
// Note the plus sign that drops any "extra" zeroes at the end.
// It changes the result (which is a string) into a number again (think "0 + foo"),
// which means that it uses only as many digits as necessary.
It seems like Math.round is a better solution. But it is not! In some cases it will not round correctly:
Math.round(1.005 * 100)/100 // Returns 1 instead of expected 1.01!
toFixed() will also not round correctly in some cases (tested in Chrome v.55.0.2883.87)!
Examples:
parseFloat("1.555").toFixed(2); // Returns 1.55 instead of 1.56.
parseFloat("1.5550").toFixed(2); // Returns 1.55 instead of 1.56.
// However, it will return correct result if you round 1.5551.
parseFloat("1.5551").toFixed(2); // Returns 1.56 as expected.
1.3555.toFixed(3) // Returns 1.355 instead of expected 1.356.
// However, it will return correct result if you round 1.35551.
1.35551.toFixed(2); // Returns 1.36 as expected.
I guess, this is because 1.555 is actually something like float 1.55499994 behind the scenes.
Solution 1 is to use a script with required rounding algorithm, for example:
function roundNumber(num, scale) {
if(!("" + num).includes("e")) {
return +(Math.round(num + "e+" + scale) + "e-" + scale);
} else {
var arr = ("" + num).split("e");
var sig = ""
if(+arr[1] + scale > 0) {
sig = "+";
}
return +(Math.round(+arr[0] + "e" + sig + (+arr[1] + scale)) + "e-" + scale);
}
}
It is also at Plunker.
Note: This is not a universal solution for everyone. There are several different rounding algorithms. Your implementation can be different, and it depends on your requirements. See also Rounding.
Solution 2 is to avoid front end calculations and pull rounded values from the backend server.
Another possible solution, which is not a bulletproof either.
Math.round((num + Number.EPSILON) * 100) / 100
In some cases, when you round a number like 1.3549999999999998, it will return an incorrect result. It should be 1.35, but the result is 1.36.
I found this on MDN. Their way avoids the problem with 1.005 that was mentioned.
function roundToTwo(num) {
return +(Math.round(num + "e+2") + "e-2");
}
console.log('1.005 => ', roundToTwo(1.005));
console.log('10 => ', roundToTwo(10));
console.log('1.7777777 => ', roundToTwo(1.7777777));
console.log('9.1 => ', roundToTwo(9.1));
console.log('1234.5678 => ', roundToTwo(1234.5678));
MarkG's answer is the correct one. Here's a generic extension for any number of decimal places.
Number.prototype.round = function(places) {
return +(Math.round(this + "e+" + places) + "e-" + places);
}
Usage:
var n = 1.7777;
n.round(2); // 1.78
Unit test:
it.only('should round floats to 2 places', function() {
var cases = [
{ n: 10, e: 10, p:2 },
{ n: 1.7777, e: 1.78, p:2 },
{ n: 1.005, e: 1.01, p:2 },
{ n: 1.005, e: 1, p:0 },
{ n: 1.77777, e: 1.8, p:1 }
]
cases.forEach(function(testCase) {
var r = testCase.n.round(testCase.p);
assert.equal(r, testCase.e, 'didn\'t get right number');
});
})
You should use:
Math.round( num * 100 + Number.EPSILON ) / 100
No one seems to be aware of Number.EPSILON.
Also it's worth noting that this is not a JavaScript weirdness like some people stated.
That is simply the way floating point numbers works in a computer. Like 99% of programming languages, JavaScript doesn't have home made floating point numbers; it relies on the CPU/FPU for that. A computer uses binary, and in binary, there isn't any numbers like 0.1, but a mere binary approximation for that. Why? For the same reason than 1/3 cannot be written in decimal: its value is 0.33333333... with an infinity of threes.
Here come Number.EPSILON. That number is the difference between 1 and the next number existing in the double precision floating point numbers. That's it: There is no number between 1 and 1 + Number.EPSILON.
EDIT:
As asked in the comments, let's clarify one thing: adding Number.EPSILON is relevant only when the value to round is the result of an arithmetic operation, as it can swallow some floating point error delta.
It's not useful when the value comes from a direct source (e.g.: literal, user input or sensor).
EDIT (2019):
Like #maganap and some peoples have pointed out, it's best to add Number.EPSILON before multiplying:
Math.round( ( num + Number.EPSILON ) * 100 ) / 100
EDIT (december 2019):
Lately, I use a function similar to this one for comparing numbers epsilon-aware:
const ESPILON_RATE = 1 + Number.EPSILON ;
const ESPILON_ZERO = Number.MIN_VALUE ;
function epsilonEquals( a , b ) {
if ( Number.isNaN( a ) || Number.isNaN( b ) ) {
return false ;
}
if ( a === 0 || b === 0 ) {
return a <= b + EPSILON_ZERO && b <= a + EPSILON_ZERO ;
}
return a <= b * EPSILON_RATE && b <= a * EPSILON_RATE ;
}
My use-case is an assertion + data validation lib I'm developing for many years.
In fact, in the code I'm using ESPILON_RATE = 1 + 4 * Number.EPSILON and EPSILON_ZERO = 4 * Number.MIN_VALUE (four times the epsilon), because I want an equality checker loose enough for cumulating floating point error.
So far, it looks perfect for me.
I hope it will help.
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.
function naiveRound(num, decimalPlaces = 0) {
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)
// testing edge cases
console.log( naiveRound(1.005, 2) ); // 1 incorrect (should be 1.01)
console.log( naiveRound(2.175, 2) ); // 2.17 incorrect (should be 2.18)
console.log( naiveRound(5.015, 2) ); // 5.01 incorrect (should be 5.02)
In order to determine whether a rounding operation involves a midpoint value, the Round function multiplies the original value to be rounded by 10 ** n, where n is the desired number of fractional digits in the return value, and then determines whether the remaining fractional portion of the value is greater than or equal to .5. This "Exact Testing for Equality" with floating-point values are problematic because of the floating-point format's issues with binary representation and precision. This means that any fractional portion of a number that is slightly less than .5 (because of a loss of precision) will not be rounded upward.
In the previous example, 5.015 is a midpoint value if it is to be rounded to two decimal places, the value of 5.015 * 100 is actually 501.49999999999994. Because .49999999999994 is less than .5, it is rounded down to 501 and finally the result is 5.01.
Better implementations
Exponential notation
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.
/**
* Round half up ('round half towards positive infinity')
* Negative numbers round differently than positive numbers.
*/
function round(num, decimalPlaces = 0) {
num = Math.round(num + "e" + decimalPlaces);
return Number(num + "e" + -decimalPlaces);
}
// test rounding of half
console.log( round(0.5) ); // 1
console.log( round(-0.5) ); // 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
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 = 0) {
if (num < 0)
return -round(-num, decimalPlaces);
num = Math.round(num + "e" + decimalPlaces);
return Number(num + "e" + -decimalPlaces);
}
Approximate rounding
To correct the rounding problem shown in the previous naiveRound example, we can define a custom rounding function that performs a "nearly equal" test to determine whether a fractional value is sufficiently close to a midpoint value to be subject to midpoint rounding.
// round half away from zero
function round(num, decimalPlaces = 0) {
if (num < 0)
return -round(-num, decimalPlaces);
var p = Math.pow(10, decimalPlaces);
var n = num * p;
var f = n - Math.floor(n);
var e = Number.EPSILON * n;
// Determine whether this fraction is a midpoint value.
return (f >= .5 - e) ? Math.ceil(n) / p : Math.floor(n) / p;
}
// test rounding of half
console.log( round(0.5) ); // 1
console.log( round(-0.5) ); // -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
Number.EPSILON
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 product before rounding. This moves to the next representable float value, away from zero, thus it will offset the binary round-off error that may occur during the multiplication by 10 ** n.
/**
* 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 = 0) {
var p = Math.pow(10, decimalPlaces);
var n = (num * p) * (1 + Number.EPSILON);
return Math.round(n) / p;
}
// rounding of half
console.log( round(0.5) ); // 1
console.log( round(-0.5) ); // -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
After adding 1 ulp, the value of 5.015 * 100 which is 501.49999999999994 will be corrected to 501.50000000000006, this will rounded up to 502 and finally the result is 5.02.
Note that the size of a unit in last place ("ulp") is determined by (1) the magnitude of the number and (2) the relative machine epsilon (2^-52). Ulps are relatively larger at numbers with bigger magnitudes than they are at numbers with smaller magnitudes.
Double rounding
Here, we use the toPrecision() method to strip the floating-point round-off errors in the intermediate calculations. Simply, we round to 15 significant figures to strip the round-off error at the 16th significant digit. This technique to preround the result to significant digits is also used by PHP 7 round function.
The value of 5.015 * 100 which is 501.49999999999994 will be rounded first to 15 significant digits as 501.500000000000, then it will rounded up again to 502 and finally the result is 5.02.
// Round half away from zero
function round(num, decimalPlaces = 0) {
if (num < 0)
return -round(-num, decimalPlaces);
var p = Math.pow(10, decimalPlaces);
var n = (num * p).toPrecision(15);
return Math.round(n) / p;
}
// rounding of half
console.log( round(0.5) ); // 1
console.log( round(-0.5) ); // -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
Arbitrary-precision JavaScript library - decimal.js
// Round half away from zero
function round(num, decimalPlaces = 0) {
return new Decimal(num).toDecimalPlaces(decimalPlaces).toNumber();
}
// rounding of half
console.log( round(0.5) ); // 1
console.log( round(-0.5) ); // -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
<script src="https://cdnjs.cloudflare.com/ajax/libs/decimal.js/10.2.1/decimal.js" integrity="sha512-GKse2KVGCCMVBn4riigHjXE8j5hCxYLPXDw8AvcjUtrt+a9TbZFtIKGdArXwYOlZvdmkhQLWQ46ZE3Q1RIa7uQ==" crossorigin="anonymous"></script>
Solution 1: string in exponential notation
Inspired by the solution provided by KFish here: https://stackoverflow.com/a/55521592/4208440
A simple drop in solution that provides accurate decimal rounding, flooring, and ceiling to a specific number of decimal places without adding a whole library. It treats floats more like decimals by fixing the binary rounding issues to avoid unexpected results: for example, floor((0.1+0.7)*10) will return the expected result 8.
Numbers are rounded to a specific number of fractional digits. Specifying a negative precision will round to any number of places to the left of the decimal point.
// Solution 1
var DecimalPrecision = (function() {
if (Math.trunc === undefined) {
Math.trunc = function(v) {
return v < 0 ? Math.ceil(v) : Math.floor(v);
};
}
var decimalAdjust = function myself(type, num, decimalPlaces) {
if (type === 'round' && num < 0)
return -myself(type, -num, decimalPlaces);
var shift = function(value, exponent) {
value = (value + 'e').split('e');
return +(value[0] + 'e' + (+value[1] + (exponent || 0)));
};
var n = shift(num, +decimalPlaces);
return shift(Math[type](n), -decimalPlaces);
};
return {
// Decimal round (half away from zero)
round: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces);
},
// Decimal ceil
ceil: function(num, decimalPlaces) {
return decimalAdjust('ceil', num, decimalPlaces);
},
// Decimal floor
floor: function(num, decimalPlaces) {
return decimalAdjust('floor', num, decimalPlaces);
},
// Decimal trunc
trunc: function(num, decimalPlaces) {
return decimalAdjust('trunc', num, decimalPlaces);
},
// Format using fixed-point notation
toFixed: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces).toFixed(decimalPlaces);
}
};
})();
// test rounding of half
console.log(DecimalPrecision.round(0.5)); // 1
console.log(DecimalPrecision.round(-0.5)); // -1
// testing very small numbers
console.log(DecimalPrecision.ceil(1e-8, 2) === 0.01);
console.log(DecimalPrecision.floor(1e-8, 2) === 0);
// testing simple cases
console.log(DecimalPrecision.round(5.12, 1) === 5.1);
console.log(DecimalPrecision.round(-5.12, 1) === -5.1);
console.log(DecimalPrecision.ceil(5.12, 1) === 5.2);
console.log(DecimalPrecision.ceil(-5.12, 1) === -5.1);
console.log(DecimalPrecision.floor(5.12, 1) === 5.1);
console.log(DecimalPrecision.floor(-5.12, 1) === -5.2);
console.log(DecimalPrecision.trunc(5.12, 1) === 5.1);
console.log(DecimalPrecision.trunc(-5.12, 1) === -5.1);
// testing edge cases for round
console.log(DecimalPrecision.round(1.005, 2) === 1.01);
console.log(DecimalPrecision.round(39.425, 2) === 39.43);
console.log(DecimalPrecision.round(-1.005, 2) === -1.01);
console.log(DecimalPrecision.round(-39.425, 2) === -39.43);
// testing edge cases for ceil
console.log(DecimalPrecision.ceil(9.13, 2) === 9.13);
console.log(DecimalPrecision.ceil(65.18, 2) === 65.18);
console.log(DecimalPrecision.ceil(-2.26, 2) === -2.26);
console.log(DecimalPrecision.ceil(-18.15, 2) === -18.15);
// testing edge cases for floor
console.log(DecimalPrecision.floor(2.26, 2) === 2.26);
console.log(DecimalPrecision.floor(18.15, 2) === 18.15);
console.log(DecimalPrecision.floor(-9.13, 2) === -9.13);
console.log(DecimalPrecision.floor(-65.18, 2) === -65.18);
// testing edge cases for trunc
console.log(DecimalPrecision.trunc(2.26, 2) === 2.26);
console.log(DecimalPrecision.trunc(18.15, 2) === 18.15);
console.log(DecimalPrecision.trunc(-2.26, 2) === -2.26);
console.log(DecimalPrecision.trunc(-18.15, 2) === -18.15);
// testing round to tens and hundreds
console.log(DecimalPrecision.round(1262.48, -1) === 1260);
console.log(DecimalPrecision.round(1262.48, -2) === 1300);
// testing toFixed()
console.log(DecimalPrecision.toFixed(1.005, 2) === "1.01");
Solution 2: purely mathematical (Number.EPSILON)
This solution avoids any string conversion / manipulation of any kind for performance reasons.
// Solution 2
var DecimalPrecision2 = (function() {
if (Number.EPSILON === undefined) {
Number.EPSILON = Math.pow(2, -52);
}
if (Math.sign === undefined) {
Math.sign = function(x) {
return ((x > 0) - (x < 0)) || +x;
};
}
return {
// Decimal round (half away from zero)
round: function(num, decimalPlaces) {
var p = Math.pow(10, decimalPlaces || 0);
var n = (num * p) * (1 + Number.EPSILON);
return Math.round(n) / p;
},
// Decimal ceil
ceil: function(num, decimalPlaces) {
var p = Math.pow(10, decimalPlaces || 0);
var n = (num * p) * (1 - Math.sign(num) * Number.EPSILON);
return Math.ceil(n) / p;
},
// Decimal floor
floor: function(num, decimalPlaces) {
var p = Math.pow(10, decimalPlaces || 0);
var n = (num * p) * (1 + Math.sign(num) * Number.EPSILON);
return Math.floor(n) / p;
},
// Decimal trunc
trunc: function(num, decimalPlaces) {
return (num < 0 ? this.ceil : this.floor)(num, decimalPlaces);
},
// Format using fixed-point notation
toFixed: function(num, decimalPlaces) {
return this.round(num, decimalPlaces).toFixed(decimalPlaces);
}
};
})();
// test rounding of half
console.log(DecimalPrecision2.round(0.5)); // 1
console.log(DecimalPrecision2.round(-0.5)); // -1
// testing very small numbers
console.log(DecimalPrecision2.ceil(1e-8, 2) === 0.01);
console.log(DecimalPrecision2.floor(1e-8, 2) === 0);
// testing simple cases
console.log(DecimalPrecision2.round(5.12, 1) === 5.1);
console.log(DecimalPrecision2.round(-5.12, 1) === -5.1);
console.log(DecimalPrecision2.ceil(5.12, 1) === 5.2);
console.log(DecimalPrecision2.ceil(-5.12, 1) === -5.1);
console.log(DecimalPrecision2.floor(5.12, 1) === 5.1);
console.log(DecimalPrecision2.floor(-5.12, 1) === -5.2);
console.log(DecimalPrecision2.trunc(5.12, 1) === 5.1);
console.log(DecimalPrecision2.trunc(-5.12, 1) === -5.1);
// testing edge cases for round
console.log(DecimalPrecision2.round(1.005, 2) === 1.01);
console.log(DecimalPrecision2.round(39.425, 2) === 39.43);
console.log(DecimalPrecision2.round(-1.005, 2) === -1.01);
console.log(DecimalPrecision2.round(-39.425, 2) === -39.43);
// testing edge cases for ceil
console.log(DecimalPrecision2.ceil(9.13, 2) === 9.13);
console.log(DecimalPrecision2.ceil(65.18, 2) === 65.18);
console.log(DecimalPrecision2.ceil(-2.26, 2) === -2.26);
console.log(DecimalPrecision2.ceil(-18.15, 2) === -18.15);
// testing edge cases for floor
console.log(DecimalPrecision2.floor(2.26, 2) === 2.26);
console.log(DecimalPrecision2.floor(18.15, 2) === 18.15);
console.log(DecimalPrecision2.floor(-9.13, 2) === -9.13);
console.log(DecimalPrecision2.floor(-65.18, 2) === -65.18);
// testing edge cases for trunc
console.log(DecimalPrecision2.trunc(2.26, 2) === 2.26);
console.log(DecimalPrecision2.trunc(18.15, 2) === 18.15);
console.log(DecimalPrecision2.trunc(-2.26, 2) === -2.26);
console.log(DecimalPrecision2.trunc(-18.15, 2) === -18.15);
// testing round to tens and hundreds
console.log(DecimalPrecision2.round(1262.48, -1) === 1260);
console.log(DecimalPrecision2.round(1262.48, -2) === 1300);
// testing toFixed()
console.log(DecimalPrecision2.toFixed(1.005, 2) === "1.01");
Solution 3: double rounding
This solution uses the toPrecision() method to strip the floating-point round-off errors.
// Solution 3
var DecimalPrecision3 = (function() {
if (Math.trunc === undefined) {
Math.trunc = function(v) {
return v < 0 ? Math.ceil(v) : Math.floor(v);
};
}
var powers = [
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7,
1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22
];
var intpow10 = function(power) {
/* Not in lookup table */
if (power < 0 || power > 22) {
return Math.pow(10, power);
}
return powers[power];
};
// Eliminate binary floating-point inaccuracies.
var stripError = function(num) {
if (Number.isInteger(num))
return num;
return parseFloat(num.toPrecision(15));
};
var decimalAdjust = function myself(type, num, decimalPlaces) {
if (type === 'round' && num < 0)
return -myself(type, -num, decimalPlaces);
var p = intpow10(decimalPlaces || 0);
var n = stripError(num * p);
return Math[type](n) / p;
};
return {
// Decimal round (half away from zero)
round: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces);
},
// Decimal ceil
ceil: function(num, decimalPlaces) {
return decimalAdjust('ceil', num, decimalPlaces);
},
// Decimal floor
floor: function(num, decimalPlaces) {
return decimalAdjust('floor', num, decimalPlaces);
},
// Decimal trunc
trunc: function(num, decimalPlaces) {
return decimalAdjust('trunc', num, decimalPlaces);
},
// Format using fixed-point notation
toFixed: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces).toFixed(decimalPlaces);
}
};
})();
// test rounding of half
console.log(DecimalPrecision3.round(0.5)); // 1
console.log(DecimalPrecision3.round(-0.5)); // -1
// testing very small numbers
console.log(DecimalPrecision3.ceil(1e-8, 2) === 0.01);
console.log(DecimalPrecision3.floor(1e-8, 2) === 0);
// testing simple cases
console.log(DecimalPrecision3.round(5.12, 1) === 5.1);
console.log(DecimalPrecision3.round(-5.12, 1) === -5.1);
console.log(DecimalPrecision3.ceil(5.12, 1) === 5.2);
console.log(DecimalPrecision3.ceil(-5.12, 1) === -5.1);
console.log(DecimalPrecision3.floor(5.12, 1) === 5.1);
console.log(DecimalPrecision3.floor(-5.12, 1) === -5.2);
console.log(DecimalPrecision3.trunc(5.12, 1) === 5.1);
console.log(DecimalPrecision3.trunc(-5.12, 1) === -5.1);
// testing edge cases for round
console.log(DecimalPrecision3.round(1.005, 2) === 1.01);
console.log(DecimalPrecision3.round(39.425, 2) === 39.43);
console.log(DecimalPrecision3.round(-1.005, 2) === -1.01);
console.log(DecimalPrecision3.round(-39.425, 2) === -39.43);
// testing edge cases for ceil
console.log(DecimalPrecision3.ceil(9.13, 2) === 9.13);
console.log(DecimalPrecision3.ceil(65.18, 2) === 65.18);
console.log(DecimalPrecision3.ceil(-2.26, 2) === -2.26);
console.log(DecimalPrecision3.ceil(-18.15, 2) === -18.15);
// testing edge cases for floor
console.log(DecimalPrecision3.floor(2.26, 2) === 2.26);
console.log(DecimalPrecision3.floor(18.15, 2) === 18.15);
console.log(DecimalPrecision3.floor(-9.13, 2) === -9.13);
console.log(DecimalPrecision3.floor(-65.18, 2) === -65.18);
// testing edge cases for trunc
console.log(DecimalPrecision3.trunc(2.26, 2) === 2.26);
console.log(DecimalPrecision3.trunc(18.15, 2) === 18.15);
console.log(DecimalPrecision3.trunc(-2.26, 2) === -2.26);
console.log(DecimalPrecision3.trunc(-18.15, 2) === -18.15);
// testing round to tens and hundreds
console.log(DecimalPrecision3.round(1262.48, -1) === 1260);
console.log(DecimalPrecision3.round(1262.48, -2) === 1300);
// testing toFixed()
console.log(DecimalPrecision3.toFixed(1.005, 2) === "1.01");
Solution 4: double rounding v2
This solution is just like Solution 3, however it uses a custom toPrecision() function.
// Solution 4
var DecimalPrecision4 = (function() {
if (Math.trunc === undefined) {
Math.trunc = function(v) {
return v < 0 ? Math.ceil(v) : Math.floor(v);
};
}
var powers = [
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7,
1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22
];
var intpow10 = function(power) {
/* Not in lookup table */
if (power < 0 || power > 22) {
return Math.pow(10, power);
}
return powers[power];
};
var toPrecision = function(num, significantDigits) {
// Return early for ±0, NaN and Infinity.
if (!num || !Number.isFinite(num))
return num;
// Compute shift of the decimal point (sf - leftSidedDigits).
var shift = significantDigits - 1 - Math.floor(Math.log10(Math.abs(num)));
// Return if rounding to the same or higher precision.
var decimalPlaces = 0;
for (var p = 1; num != Math.round(num * p) / p; p *= 10) decimalPlaces++;
if (shift >= decimalPlaces)
return num;
// Round to "shift" fractional digits
var scale = intpow10(Math.abs(shift));
return shift > 0 ?
Math.round(num * scale) / scale :
Math.round(num / scale) * scale;
};
// Eliminate binary floating-point inaccuracies.
var stripError = function(num) {
if (Number.isInteger(num))
return num;
return toPrecision(num, 15);
};
var decimalAdjust = function myself(type, num, decimalPlaces) {
if (type === 'round' && num < 0)
return -myself(type, -num, decimalPlaces);
var p = intpow10(decimalPlaces || 0);
var n = stripError(num * p);
return Math[type](n) / p;
};
return {
// Decimal round (half away from zero)
round: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces);
},
// Decimal ceil
ceil: function(num, decimalPlaces) {
return decimalAdjust('ceil', num, decimalPlaces);
},
// Decimal floor
floor: function(num, decimalPlaces) {
return decimalAdjust('floor', num, decimalPlaces);
},
// Decimal trunc
trunc: function(num, decimalPlaces) {
return decimalAdjust('trunc', num, decimalPlaces);
},
// Format using fixed-point notation
toFixed: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces).toFixed(decimalPlaces);
}
};
})();
// test rounding of half
console.log(DecimalPrecision4.round(0.5)); // 1
console.log(DecimalPrecision4.round(-0.5)); // -1
// testing very small numbers
console.log(DecimalPrecision4.ceil(1e-8, 2) === 0.01);
console.log(DecimalPrecision4.floor(1e-8, 2) === 0);
// testing simple cases
console.log(DecimalPrecision4.round(5.12, 1) === 5.1);
console.log(DecimalPrecision4.round(-5.12, 1) === -5.1);
console.log(DecimalPrecision4.ceil(5.12, 1) === 5.2);
console.log(DecimalPrecision4.ceil(-5.12, 1) === -5.1);
console.log(DecimalPrecision4.floor(5.12, 1) === 5.1);
console.log(DecimalPrecision4.floor(-5.12, 1) === -5.2);
console.log(DecimalPrecision4.trunc(5.12, 1) === 5.1);
console.log(DecimalPrecision4.trunc(-5.12, 1) === -5.1);
// testing edge cases for round
console.log(DecimalPrecision4.round(1.005, 2) === 1.01);
console.log(DecimalPrecision4.round(39.425, 2) === 39.43);
console.log(DecimalPrecision4.round(-1.005, 2) === -1.01);
console.log(DecimalPrecision4.round(-39.425, 2) === -39.43);
// testing edge cases for ceil
console.log(DecimalPrecision4.ceil(9.13, 2) === 9.13);
console.log(DecimalPrecision4.ceil(65.18, 2) === 65.18);
console.log(DecimalPrecision4.ceil(-2.26, 2) === -2.26);
console.log(DecimalPrecision4.ceil(-18.15, 2) === -18.15);
// testing edge cases for floor
console.log(DecimalPrecision4.floor(2.26, 2) === 2.26);
console.log(DecimalPrecision4.floor(18.15, 2) === 18.15);
console.log(DecimalPrecision4.floor(-9.13, 2) === -9.13);
console.log(DecimalPrecision4.floor(-65.18, 2) === -65.18);
// testing edge cases for trunc
console.log(DecimalPrecision4.trunc(2.26, 2) === 2.26);
console.log(DecimalPrecision4.trunc(18.15, 2) === 18.15);
console.log(DecimalPrecision4.trunc(-2.26, 2) === -2.26);
console.log(DecimalPrecision4.trunc(-18.15, 2) === -18.15);
// testing round to tens and hundreds
console.log(DecimalPrecision4.round(1262.48, -1) === 1260);
console.log(DecimalPrecision4.round(1262.48, -2) === 1300);
// testing toFixed()
console.log(DecimalPrecision4.toFixed(1.005, 2) === "1.01");
Benchmarks
http://jsbench.github.io/#31ec3a8b3d22bd840f8e6822e681a3ac
Here is a benchmark comparing the operations per second in the solutions above on Chrome 109.0.0.0. Rounding functions using the Number.EPSILON is at least 10x-20x faster. Obviously all browsers differ, so your mileage may vary.
(Note: More is better)
Thanks #Mike for adding a screenshot of the benchmark.
This question is complicated.
Suppose we have a function, roundTo2DP(num), that takes a float as an argument and returns a value rounded to 2 decimal places. What should each of these expressions evaluate to?
roundTo2DP(0.014999999999999999)
roundTo2DP(0.0150000000000000001)
roundTo2DP(0.015)
The 'obvious' answer is that the first example should round to 0.01 (because it's closer to 0.01 than to 0.02) while the other two should round to 0.02 (because 0.0150000000000000001 is closer to 0.02 than to 0.01, and because 0.015 is exactly halfway between them and there is a mathematical convention that such numbers get rounded up).
The catch, which you may have guessed, is that roundTo2DP cannot possibly be implemented to give those obvious answers, because all three numbers passed to it are the same number. IEEE 754 binary floating point numbers (the kind used by JavaScript) can't exactly represent most non-integer numbers, and so all three numeric literals above get rounded to a nearby valid floating point number. This number, as it happens, is exactly
0.01499999999999999944488848768742172978818416595458984375
which is closer to 0.01 than to 0.02.
You can see that all three numbers are the same at your browser console, Node shell, or other JavaScript interpreter. Just compare them:
> 0.014999999999999999 === 0.0150000000000000001
true
So when I write m = 0.0150000000000000001, the exact value of m that I end up with is closer to 0.01 than it is to 0.02. And yet, if I convert m to a String...
> var m = 0.0150000000000000001;
> console.log(String(m));
0.015
> var m = 0.014999999999999999;
> console.log(String(m));
0.015
... I get 0.015, which should round to 0.02, and which is noticeably not the 56-decimal-place number I earlier said that all of these numbers were exactly equal to. So what dark magic is this?
The answer can be found in the ECMAScript specification, in section 7.1.12.1: ToString applied to the Number type. Here the rules for converting some Number m to a String are laid down. The key part is point 5, in which an integer s is generated whose digits will be used in the String representation of m:
let n, k, and s be integers such that k ≥ 1, 10k-1 ≤ s < 10k, the Number value for s × 10n-k is m, and k is as small as possible. Note that k is the number of digits in the decimal representation of s, that s is not divisible by 10, and that the least significant digit of s is not necessarily uniquely determined by these criteria.
The key part here is the requirement that "k is as small as possible". What that requirement amounts to is a requirement that, given a Number m, the value of String(m) must have the least possible number of digits while still satisfying the requirement that Number(String(m)) === m. Since we already know that 0.015 === 0.0150000000000000001, it's now clear why String(0.0150000000000000001) === '0.015' must be true.
Of course, none of this discussion has directly answered what roundTo2DP(m) should return. If m's exact value is 0.01499999999999999944488848768742172978818416595458984375, but its String representation is '0.015', then what is the correct answer - mathematically, practically, philosophically, or whatever - when we round it to two decimal places?
There is no single correct answer to this. It depends upon your use case. You probably want to respect the String representation and round upwards when:
The value being represented is inherently discrete, e.g. an amount of currency in a 3-decimal-place currency like dinars. In this case, the true value of a Number like 0.015 is 0.015, and the 0.0149999999... representation that it gets in binary floating point is a rounding error. (Of course, many will argue, reasonably, that you should use a decimal library for handling such values and never represent them as binary floating point Numbers in the first place.)
The value was typed by a user. In this case, again, the exact decimal number entered is more 'true' than the nearest binary floating point representation.
On the other hand, you probably want to respect the binary floating point value and round downwards when your value is from an inherently continuous scale - for instance, if it's a reading from a sensor.
These two approaches require different code. To respect the String representation of the Number, we can (with quite a bit of reasonably subtle code) implement our own rounding that acts directly on the String representation, digit by digit, using the same algorithm you would've used in school when you were taught how to round numbers. Below is an example which respects the OP's requirement of representing the number to 2 decimal places "only when necessary" by stripping trailing zeroes after the decimal point; you may, of course, need to tweak it to your precise needs.
/**
* Converts num to a decimal string (if it isn't one already) and then rounds it
* to at most dp decimal places.
*
* For explanation of why you'd want to perform rounding operations on a String
* rather than a Number, see http://stackoverflow.com/a/38676273/1709587
*
* #param {(number|string)} num
* #param {number} dp
* #return {string}
*/
function roundStringNumberWithoutTrailingZeroes (num, dp) {
if (arguments.length != 2) throw new Error("2 arguments required");
num = String(num);
if (num.indexOf('e+') != -1) {
// Can't round numbers this large because their string representation
// contains an exponent, like 9.99e+37
throw new Error("num too large");
}
if (num.indexOf('.') == -1) {
// Nothing to do
return num;
}
if (num[0] == '-') {
return "-" + roundStringNumberWithoutTrailingZeroes(num.slice(1), dp)
}
var parts = num.split('.'),
beforePoint = parts[0],
afterPoint = parts[1],
shouldRoundUp = afterPoint[dp] >= 5,
finalNumber;
afterPoint = afterPoint.slice(0, dp);
if (!shouldRoundUp) {
finalNumber = beforePoint + '.' + afterPoint;
} else if (/^9+$/.test(afterPoint)) {
// If we need to round up a number like 1.9999, increment the integer
// before the decimal point and discard the fractional part.
// We want to do this while still avoiding converting the whole
// beforePart to a Number (since that could cause loss of precision if
// beforePart is bigger than Number.MAX_SAFE_INTEGER), so the logic for
// this is once again kinda complicated.
// Note we can (and want to) use early returns here because the
// zero-stripping logic at the end of
// roundStringNumberWithoutTrailingZeroes does NOT apply here, since
// the result is a whole number.
if (/^9+$/.test(beforePoint)) {
return "1" + beforePoint.replaceAll("9", "0")
}
// Starting from the last digit, increment digits until we find one
// that is not 9, then stop
var i = beforePoint.length - 1;
while (true) {
if (beforePoint[i] == '9') {
beforePoint = beforePoint.substr(0, i) +
'0' +
beforePoint.substr(i+1);
i--;
} else {
beforePoint = beforePoint.substr(0, i) +
(Number(beforePoint[i]) + 1) +
beforePoint.substr(i+1);
break;
}
}
return beforePoint
} else {
// Starting from the last digit, increment digits until we find one
// that is not 9, then stop
var i = dp-1;
while (true) {
if (afterPoint[i] == '9') {
afterPoint = afterPoint.substr(0, i) +
'0' +
afterPoint.substr(i+1);
i--;
} else {
afterPoint = afterPoint.substr(0, i) +
(Number(afterPoint[i]) + 1) +
afterPoint.substr(i+1);
break;
}
}
finalNumber = beforePoint + '.' + afterPoint;
}
// Remove trailing zeroes from fractional part before returning
return finalNumber.replace(/0+$/, '')
}
Example usage:
> roundStringNumberWithoutTrailingZeroes(1.6, 2)
'1.6'
> roundStringNumberWithoutTrailingZeroes(10000, 2)
'10000'
> roundStringNumberWithoutTrailingZeroes(0.015, 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes('0.015000', 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes(1, 1)
'1'
> roundStringNumberWithoutTrailingZeroes('0.015', 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes(0.01499999999999999944488848768742172978818416595458984375, 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes('0.01499999999999999944488848768742172978818416595458984375', 2)
'0.01'
> roundStringNumberWithoutTrailingZeroes('16.996', 2)
'17'
The function above is probably what you want to use to avoid users ever witnessing numbers that they have entered being rounded wrongly.
(As an alternative, you could also try the round10 library which provides a similarly-behaving function with a wildly different implementation.)
But what if you have the second kind of Number - a value taken from a continuous scale, where there's no reason to think that approximate decimal representations with fewer decimal places are more accurate than those with more? In that case, we don't want to respect the String representation, because that representation (as explained in the spec) is already sort-of-rounded; we don't want to make the mistake of saying "0.014999999...375 rounds up to 0.015, which rounds up to 0.02, so 0.014999999...375 rounds up to 0.02".
Here we can simply use the built-in toFixed method. Note that by calling Number() on the String returned by toFixed, we get a Number whose String representation has no trailing zeroes (thanks to the way JavaScript computes the String representation of a Number, discussed earlier in this answer).
/**
* Takes a float and rounds it to at most dp decimal places. For example
*
* roundFloatNumberWithoutTrailingZeroes(1.2345, 3)
*
* returns 1.234
*
* Note that since this treats the value passed to it as a floating point
* number, it will have counterintuitive results in some cases. For instance,
*
* roundFloatNumberWithoutTrailingZeroes(0.015, 2)
*
* gives 0.01 where 0.02 might be expected. For an explanation of why, see
* http://stackoverflow.com/a/38676273/1709587. You may want to consider using the
* roundStringNumberWithoutTrailingZeroes function there instead.
*
* #param {number} num
* #param {number} dp
* #return {number}
*/
function roundFloatNumberWithoutTrailingZeroes (num, dp) {
var numToFixedDp = Number(num).toFixed(dp);
return Number(numToFixedDp);
}
Consider .toFixed() and .toPrecision():
http://www.javascriptkit.com/javatutors/formatnumber.shtml
One can use .toFixed(NumberOfDecimalPlaces).
var str = 10.234.toFixed(2); // => '10.23'
var number = Number(str); // => 10.23
Here is a simple way to do it:
Math.round(value * 100) / 100
You might want to go ahead and make a separate function to do it for you though:
function roundToTwo(value) {
return(Math.round(value * 100) / 100);
}
Then you would simply pass in the value.
You could enhance it to round to any arbitrary number of decimals by adding a second parameter.
function myRound(value, places) {
var multiplier = Math.pow(10, places);
return (Math.round(value * multiplier) / multiplier);
}
A precise rounding method. Source: Mozilla
(function(){
/**
* Decimal adjustment of a number.
*
* #param {String} type The type of adjustment.
* #param {Number} value The number.
* #param {Integer} exp The exponent (the 10 logarithm of the adjustment base).
* #returns {Number} The adjusted value.
*/
function decimalAdjust(type, value, exp) {
// If the exp is undefined or zero...
if (typeof exp === 'undefined' || +exp === 0) {
return Math[type](value);
}
value = +value;
exp = +exp;
// If the value is not a number or the exp is not an integer...
if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
return NaN;
}
// Shift
value = value.toString().split('e');
value = Math[type](+(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));
}
// Decimal round
if (!Math.round10) {
Math.round10 = function(value, exp) {
return decimalAdjust('round', value, exp);
};
}
// Decimal floor
if (!Math.floor10) {
Math.floor10 = function(value, exp) {
return decimalAdjust('floor', value, exp);
};
}
// Decimal ceil
if (!Math.ceil10) {
Math.ceil10 = function(value, exp) {
return decimalAdjust('ceil', value, exp);
};
}
})();
Examples:
// Round
Math.round10(55.55, -1); // 55.6
Math.round10(55.549, -1); // 55.5
Math.round10(55, 1); // 60
Math.round10(54.9, 1); // 50
Math.round10(-55.55, -1); // -55.5
Math.round10(-55.551, -1); // -55.6
Math.round10(-55, 1); // -50
Math.round10(-55.1, 1); // -60
Math.round10(1.005, -2); // 1.01 -- compare this with Math.round(1.005*100)/100 above
// Floor
Math.floor10(55.59, -1); // 55.5
Math.floor10(59, 1); // 50
Math.floor10(-55.51, -1); // -55.6
Math.floor10(-51, 1); // -60
// Ceil
Math.ceil10(55.51, -1); // 55.6
Math.ceil10(51, 1); // 60
Math.ceil10(-55.59, -1); // -55.5
Math.ceil10(-59, 1); // -50
None of the answers found here is correct. stinkycheeseman asked to round up, but you all rounded the number.
To round up, use this:
Math.ceil(num * 100)/100;
This may help you:
var result = Math.round(input*100)/100;
For more information, you can have a look at Math.round(num) vs num.toFixed(0) and browser inconsistencies
For me Math.round() was not giving correct answer. I found toFixed(2) works better.
Below are examples of both:
console.log(Math.round(43000 / 80000) * 100); // wrong answer
console.log(((43000 / 80000) * 100).toFixed(2)); // correct answer
Use this function Number(x).toFixed(2);
If you are using the Lodash library, you can use the round method of Lodash like following.
_.round(number, precision)
For example:
_.round(1.7777777, 2) = 1.78
+(10).toFixed(2); // = 10
+(10.12345).toFixed(2); // = 10.12
(10).toFixed(2); // = 10.00
(10.12345).toFixed(2); // = 10.12
Try this lightweight solution:
function round(x, digits){
return parseFloat(x.toFixed(digits))
}
round(1.222, 2);
// 1.22
round(1.222, 10);
// 1.222
There are a couple of ways to do that. For people like me, Lodash's variant
function round(number, precision) {
var pair = (number + 'e').split('e')
var value = Math.round(pair[0] + 'e' + (+pair[1] + precision))
pair = (value + 'e').split('e')
return +(pair[0] + 'e' + (+pair[1] - precision))
}
Usage:
round(0.015, 2) // 0.02
round(1.005, 2) // 1.01
If your project uses jQuery or Lodash, you can also find the proper round method in the libraries.
2017
Just use native code .toFixed()
number = 1.2345;
number.toFixed(2) // "1.23"
If you need to be strict and add digits just if needed it can use replace
number = 1; // "1"
number.toFixed(5).replace(/\.?0*$/g,'');
Another simple solution (without writing any function) may to use toFixed() and then convert to float again:
For example:
var objNumber = 1201203.1256546456;
objNumber = parseFloat(objNumber.toFixed(2))
Since ES6 there is a 'proper' way (without overriding statics and creating workarounds) to do this by using toPrecision
var x = 1.49999999999;
console.log(x.toPrecision(4));
console.log(x.toPrecision(3));
console.log(x.toPrecision(2));
var y = Math.PI;
console.log(y.toPrecision(6));
console.log(y.toPrecision(5));
console.log(y.toPrecision(4));
var z = 222.987654
console.log(z.toPrecision(6));
console.log(z.toPrecision(5));
console.log(z.toPrecision(4));
then you can just parseFloat and zeroes will 'go away'.
console.log(parseFloat((1.4999).toPrecision(3)));
console.log(parseFloat((1.005).toPrecision(3)));
console.log(parseFloat((1.0051).toPrecision(3)));
It doesn't solve the '1.005 rounding problem' though - since it is intrinsic to how float fractions are being processed.
console.log(1.005 - 0.005);
If you are open to libraries you can use bignumber.js
console.log(1.005 - 0.005);
console.log(new BigNumber(1.005).minus(0.005));
console.log(new BigNumber(1.005).round(4));
console.log(new BigNumber(1.005).round(3));
console.log(new BigNumber(1.005).round(2));
console.log(new BigNumber(1.005).round(1));
<script src="https://cdnjs.cloudflare.com/ajax/libs/bignumber.js/2.3.0/bignumber.min.js"></script>
The easiest approach would be to use toFixed and then strip trailing zeros using the Number function:
const number = 15.5;
Number(number.toFixed(2)); // 15.5
const number = 1.7777777;
Number(number.toFixed(2)); // 1.78
One way to achieve such a rounding only if necessary is to use Number.prototype.toLocaleString():
myNumber.toLocaleString('en', {maximumFractionDigits:2, useGrouping:false})
This will provide exactly the output you expect, but as strings. You can still convert those back to numbers if that's not the data type you expect.
MarkG and Lavamantis offered a much better solution than the one that has been accepted. It's a shame they don't get more upvotes!
Here is the function I use to solve the floating point decimals issues also based on MDN. It is even more generic (but less concise) than Lavamantis's solution:
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));
}
Use it with:
round(10.8034, 2); // Returns 10.8
round(1.275, 2); // Returns 1.28
round(1.27499, 2); // Returns 1.27
round(1.2345678e+2, 2); // Returns 123.46
Compared to Lavamantis's solution, we can do...
round(1234.5678, -2); // Returns 1200
round("123.45"); // Returns 123
It may work for you,
Math.round(num * 100)/100;
to know the difference between toFixed and round. You can have a look at Math.round(num) vs num.toFixed(0) and browser inconsistencies.
Keep the type as integer for later sorting or other arithmetic operations:
Math.round(1.7777777 * 100)/100
1.78
// Round up!
Math.ceil(1.7777777 * 100)/100
1.78
// Round down!
Math.floor(1.7777777 * 100)/100
1.77
Or convert to a string:
(1.7777777).toFixed(2)
"1.77"
This is the simplest, more elegant solution (and I am the best of the world;):
function roundToX(num, X) {
return +(Math.round(num + "e+"+X) + "e-"+X);
}
//roundToX(66.66666666,2) => 66.67
//roundToX(10,2) => 10
//roundToX(10.904,2) => 10.9
Modern syntax alternative with fallback values
const roundToX = (num = 0, X = 20) => +(Math.round(num + `e${X}`) + `e-${X}`)
var roundUpto = function(number, upto){
return Number(number.toFixed(upto));
}
roundUpto(0.1464676, 2);
toFixed(2): Here 2 is the number of digits up to which we want to round this number.
See AmrAli's answer for a more thorough run through and performance breakdown of all the various adaptations of this solution.
var DecimalPrecision = (function(){
if (Number.EPSILON === undefined) {
Number.EPSILON = Math.pow(2, -52);
}
if(Number.isInteger === undefined){
Number.isInteger = function(value) {
return typeof value === 'number' &&
isFinite(value) &&
Math.floor(value) === value;
};
}
this.isRound = function(n,p){
let l = n.toString().split('.')[1].length;
return (p >= l);
}
this.round = function(n, p=2){
if(Number.isInteger(n) || this.isRound(n,p))
return n;
let r = 0.5 * Number.EPSILON * n;
let o = 1; while(p-- > 0) o *= 10;
if(n<0)
o *= -1;
return Math.round((n + r) * o) / o;
}
this.ceil = function(n, p=2){
if(Number.isInteger(n) || this.isRound(n,p))
return n;
let r = 0.5 * Number.EPSILON * n;
let o = 1; while(p-- > 0) o *= 10;
return Math.ceil((n + r) * o) / o;
}
this.floor = function(n, p=2){
if(Number.isInteger(n) || this.isRound(n,p))
return n;
let r = 0.5 * Number.EPSILON * n;
let o = 1; while(p-- > 0) o *= 10;
return Math.floor((n + r) * o) / o;
}
return this;
})();
console.log(DecimalPrecision.round(1.005));
console.log(DecimalPrecision.ceil(1.005));
console.log(DecimalPrecision.floor(1.005));
console.log(DecimalPrecision.round(1.0049999));
console.log(DecimalPrecision.ceil(1.0049999));
console.log(DecimalPrecision.floor(1.0049999));
console.log(DecimalPrecision.round(2.175495134384,7));
console.log(DecimalPrecision.round(2.1753543549,8));
console.log(DecimalPrecision.round(2.1755465135353,4));
console.log(DecimalPrecision.ceil(17,4));
console.log(DecimalPrecision.ceil(17.1,4));
console.log(DecimalPrecision.ceil(17.1,15));
in JavaScript, the typical way to round a number to N decimal places is something like:
function roundNumber(num, dec) {
return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
}
function roundNumber(num, dec) {
return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
}
console.log(roundNumber(0.1 + 0.2, 2));
console.log(roundNumber(2.1234, 2));
However this approach will round to a maximum of N decimal places while I want to always round to N decimal places. For example "2.0" would be rounded to "2".
Any ideas?
I think that there is a more simple approach to all given here, and is the method Number.toFixed() already implemented in JavaScript.
simply write:
var myNumber = 2;
myNumber.toFixed(2); //returns "2.00"
myNumber.toFixed(1); //returns "2.0"
etc...
I found a way. This is Christoph's code with a fix:
function toFixed(value, precision) {
var precision = precision || 0,
power = Math.pow(10, precision),
absValue = Math.abs(Math.round(value * power)),
result = (value < 0 ? '-' : '') + String(Math.floor(absValue / power));
if (precision > 0) {
var fraction = String(absValue % power),
padding = new Array(Math.max(precision - fraction.length, 0) + 1).join('0');
result += '.' + padding + fraction;
}
return result;
}
Read the details of repeating a character using an array constructor here if you are curious as to why I added the "+ 1".
That's not a rounding ploblem, that is a display problem. A number doesn't contain information about significant digits; the value 2 is the same as 2.0000000000000. It's when you turn the rounded value into a string that you have make it display a certain number of digits.
You could just add zeroes after the number, something like:
var s = number.toString();
if (s.indexOf('.') == -1) s += '.';
while (s.length < s.indexOf('.') + 4) s += '0';
(Note that this assumes that the regional settings of the client uses period as decimal separator, the code needs some more work to function for other settings.)
There's always a better way for doing things. Use toPrecision -
var number = 51.93999999999761;
I would like to get four digits precision: 51.94
just do:
number.toPrecision(4);
the result will be: 51.94
This works for rounding to N digits (if you just want to truncate to N digits remove the Math.round call and use the Math.trunc one):
function roundN(value, digits) {
var tenToN = 10 ** digits;
return /*Math.trunc*/(Math.round(value * tenToN)) / tenToN;
}
Had to resort to such logic at Java in the past when I was authoring data manipulation E-Slate components. That is since I had found out that adding 0.1 many times to 0 you'd end up with some unexpectedly long decimal part (this is due to floating point arithmetics).
A user comment at Format number to always show 2 decimal places calls this technique scaling.
Some mention there are cases that don't round as expected and at http://www.jacklmoore.com/notes/rounding-in-javascript/ this is suggested instead:
function round(value, decimals) {
return Number(Math.round(value+'e'+decimals)+'e-'+decimals);
}
PHP-Like rounding Method
The code below can be used to add your own version of Math.round to your own namespace which takes a precision parameter. Unlike Decimal rounding in the example above, this performs no conversion to and from strings, and the precision parameter works same way as PHP and Excel whereby a positive 1 would round to 1 decimal place and -1 would round to the tens.
var myNamespace = {};
myNamespace.round = function(number, precision) {
var factor = Math.pow(10, precision);
var tempNumber = number * factor;
var roundedTempNumber = Math.round(tempNumber);
return roundedTempNumber / factor;
};
myNamespace.round(1234.5678, 1); // 1234.6
myNamespace.round(1234.5678, -1); // 1230
from Mozilla Developer reference for Math.round()
Hopefully working code (didn't do much testing):
function toFixed(value, precision) {
var precision = precision || 0,
neg = value < 0,
power = Math.pow(10, precision),
value = Math.round(value * power),
integral = String((neg ? Math.ceil : Math.floor)(value / power)),
fraction = String((neg ? -value : value) % power),
padding = new Array(Math.max(precision - fraction.length, 0) + 1).join('0');
return precision ? integral + '.' + padding + fraction : integral;
}
I think below function can help
function roundOff(value,round) {
return (parseInt(value * (10 ** (round + 1))) - parseInt(value * (10 ** round)) * 10) > 4 ? (((parseFloat(parseInt((value + parseFloat(1 / (10 ** round))) * (10 ** round))))) / (10 ** round)) : (parseFloat(parseInt(value * (10 ** round))) / ( 10 ** round));
}
usage : roundOff(600.23458,2); will return 600.23
function roundton(num, n) {
return Number(num.toFixed(n));
}
This uses JS's built-in method Number.prototype.toFixed which is meant for formatting strings but allows us to round to a specific number of digits. the Number() call converts it back to a number object cleanly
Ideally, we wouldn't need to convert it to a string, but toFixed is written in native C++ doing basic cstring operations so it's likely still fast.
If you do not really care about rounding, just added a toFixed(x) and then removing trailing 0es and the dot if necessary. It is not a fast solution.
function format(value, decimals) {
if (value) {
value = value.toFixed(decimals);
} else {
value = "0";
}
if (value.indexOf(".") < 0) { value += "."; }
var dotIdx = value.indexOf(".");
while (value.length - dotIdx <= decimals) { value += "0"; } // add 0's
return value;
}
Can you round a number in javascript to 1 character after the decimal point (properly rounded)?
I tried the *10, round, /10 but it leaves two decimals at the end of the int.
Math.round(num * 10) / 10 works, here is an example...
var number = 12.3456789
var rounded = Math.round(number * 10) / 10
// rounded is 12.3
if you want it to have one decimal place, even when that would be a 0, then add...
var fixed = rounded.toFixed(1)
// fixed is always to 1 d.p.
// NOTE: .toFixed() returns a string!
// To convert back to number format
parseFloat(number.toFixed(2))
// 12.34
// but that will not retain any trailing zeros
// So, just make sure it is the last step before output,
// and use a number format during calculations!
EDIT: Add round with precision function...
Using this principle, for reference, here is a handy little round function that takes precision...
function round(value, precision) {
var multiplier = Math.pow(10, precision || 0);
return Math.round(value * multiplier) / multiplier;
}
... usage ...
round(12345.6789, 2) // 12345.68
round(12345.6789, 1) // 12345.7
... defaults to round to nearest whole number (precision 0) ...
round(12345.6789) // 12346
... and can be used to round to nearest 10 or 100 etc...
round(12345.6789, -1) // 12350
round(12345.6789, -2) // 12300
... and correct handling of negative numbers ...
round(-123.45, 1) // -123.4
round(123.45, 1) // 123.5
... and can be combined with toFixed to format consistently as string ...
round(456.7, 2).toFixed(2) // "456.70"
var number = 123.456;
console.log(number.toFixed(1)); // should round to 123.5
If you use Math.round(5.01) you will get 5 instead of 5.0.
If you use toFixed you run into rounding issues.
If you want the best of both worlds, combine the two:
(Math.round(5.01 * 10) / 10).toFixed(1)
You might want to create a function for this:
function roundedToFixed(input, digits){
var rounder = Math.pow(10, digits);
return (Math.round(input * rounder) / rounder).toFixed(digits);
}
lodash has a round method:
_.round(4.006);
// => 4
_.round(4.006, 2);
// => 4.01
_.round(4060, -2);
// => 4100
Docs.
Source.
You can simply do the following:
let n = 1.25
let result = Number(n).toFixed(1)
// output string: 1.3
I vote for toFixed(), but, for the record, here's another way that uses bit shifting to cast the number to an int. So, it always rounds towards zero (down for positive numbers, up for negatives).
var rounded = ((num * 10) << 0) * 0.1;
But hey, since there are no function calls, it's wicked fast. :)
And here's one that uses string matching:
var rounded = (num + '').replace(/(^.*?\d+)(\.\d)?.*/, '$1$2');
I don't recommend using the string variant, just sayin.
Try with this:
var original=28.453
// 1.- round "original" to two decimals
var result = Math.round (original * 100) / 100 //returns 28.45
// 2.- round "original" to 1 decimal
var result = Math.round (original * 10) / 10 //returns 28.5
// 3.- round 8.111111 to 3 decimals
var result = Math.round (8.111111 * 1000) / 1000 //returns 8.111
less complicated and easier to implement...
with this, you can create a function to do:
function RoundAndFix (n, d) {
var m = Math.pow (10, d);
return Math.round (n * m) / m;
}
function RoundAndFix (n, d) {
var m = Math.pow (10, d);
return Math.round (n * m) / m;
}
console.log (RoundAndFix(8.111111, 3));
EDIT: see this How to round using ROUND HALF UP. Rounding mode that most of us were taught in grade school
Why not just
let myNumber = 213.27321;
+myNumber.toFixed(1); // => 213.3
toFixed:
returns a string representing the given number using fixed-point notation.
Unary plus (+): The unary plus operator precedes its operand and evaluates to its operand but attempts to convert it into a number, if it isn't already.
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.
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)
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.
/**
* 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
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.
/**
* 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
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).
Using toPrecision method:
var a = 1.2345
a.toPrecision(2)
// result "1.2"
var num = 34.7654;
num = Math.round(num * 10) / 10;
console.log(num); // Logs: 34.8
To complete the Best Answer:
var round = function ( number, precision )
{
precision = precision || 0;
return parseFloat( parseFloat( number ).toFixed( precision ) );
}
The input parameter number may "not" always be a number, in this case .toFixed does not exist.
ES 6 Version of Accepted Answer:
function round(value, precision) {
const multiplier = 10 ** (precision || 0);
return Math.round(value * multiplier) / multiplier;
}
If your method does not work, plz post your code.
However,you could accomplish the rounding off task as:
var value = Math.round(234.567*100)/100
Will give you 234.56
Similarly
var value = Math.round(234.567*10)/10
Will give 234.5
In this way you can use a variable in the place of the constant as used above.
I made one that returns number type and also places decimals only if are needed (no 0 padding).
Examples:
roundWithMaxPrecision(11.234, 2); //11.23
roundWithMaxPrecision(11.234, 1); //11.2
roundWithMaxPrecision(11.234, 4); //11.23
roundWithMaxPrecision(11.234, -1); //10
roundWithMaxPrecision(4.2, 2); //4.2
roundWithMaxPrecision(4.88, 1); //4.9
The code:
function roundWithMaxPrecision (n, precision) {
const precisionWithPow10 = Math.pow(10, precision);
return Math.round(n * precisionWithPow10) / precisionWithPow10;
}
Little Angular filter if anyone wants it:
angular.module('filters').filter('decimalPlace', function() {
return function(num, precision) {
var multiplier = Math.pow(10, precision || 0);
return Math.round(num * multiplier) / multiplier;
};
});
use if via:
{{model.value| decimalPlace}}
{{model.value| decimalPlace:1}}
{{model.value| decimalPlace:2}}
:)
This seems to work reliably across anything I throw at it:
function round(val, multiplesOf) {
var s = 1 / multiplesOf;
var res = Math.ceil(val*s)/s;
res = res < val ? res + multiplesOf: res;
var afterZero = multiplesOf.toString().split(".")[1];
return parseFloat(res.toFixed(afterZero ? afterZero.length : 0));
}
It rounds up, so you may need to modify it according to use case. This should work:
console.log(round(10.01, 1)); //outputs 11
console.log(round(10.01, 0.1)); //outputs 10.1
If you care about proper rounding up then:
function roundNumericStrings(str , numOfDecPlacesRequired){
var roundFactor = Math.pow(10, numOfDecPlacesRequired);
return (Math.round(parseFloat(str)*roundFactor)/roundFactor).toString(); }
Else if you don't then you already have a reply from previous posts
str.slice(0, -1)
Math.round( num * 10) / 10 doesn't work.
For example, 1455581777.8-145558160.4 gives you 1310023617.3999999.
So only use num.toFixed(1)
I found a way to avoid the precision problems:
function badRound (num, precision) {
const x = 10 ** precision;
return Math.round(num * x) / x
}
// badRound(1.005, 2) --> 1
function round (num, precision) {
const x = 10 ** (precision + 1);
const y = 10 ** precision;
return Math.round(Math.round(num * x) / 10) / y
}
// round(1.005, 2) --> 1.01
Math.round( mul/count * 10 ) / 10
Math.round(Math.sqrt(sqD/y) * 10 ) / 10
Thanks
function rnd(v,n=2) {
return Math.round((v+Number.EPSILON)*Math.pow(10,n))/Math.pow(10,n)
}
this one catch the corner cases well
If your source code is typescript you could use a function like this:
public static ToFixedRounded(decimalNumber: number, fractionDigits: number): number {
var rounded = Math.pow(10, fractionDigits);
return (Math.round(decimalNumber * rounded) / rounded).toFixed(fractionDigits) as unknown as number;
}
const solds = 136780000000;
const number = (solds >= 1000000000 && solds < 1000000000000) ? { divisor: 1000000000, postfix: "B" }: (solds >= 1000000 && solds < 1000000000) ? { divisor: 1000000, postfix: "M" }: (solds >= 1000 && solds < 1000000) ? { divisor: 1000, postfix: "K" }: { divisor: 1, postfix: null };
const floor = Math.floor(solds / number.divisor).toLocaleString();
const firstDecimalIndex = solds.toLocaleString().charAt(floor.length+1);
const final =firstDecimalIndex.match("0")? floor + number.postfix: floor + "." + firstDecimalIndex + number.postfix;
console.log(final);
136780000000 --> 136.7B
1367800 --> 1.3M
1342 --> 1.3K
I'd like to round at most two decimal places, but only if necessary.
Input:
10
1.7777777
9.1
Output:
10
1.78
9.1
How can I do this in JavaScript?
Use Math.round() :
Math.round(num * 100) / 100
Or to be more specific and to ensure things like 1.005 round correctly, use Number.EPSILON :
Math.round((num + Number.EPSILON) * 100) / 100
If the value is a text type:
parseFloat("123.456").toFixed(2);
If the value is a number:
var numb = 123.23454;
numb = numb.toFixed(2);
There is a downside that values like 1.5 will give "1.50" as the output. A fix suggested by #minitech:
var numb = 1.5;
numb = +numb.toFixed(2);
// Note the plus sign that drops any "extra" zeroes at the end.
// It changes the result (which is a string) into a number again (think "0 + foo"),
// which means that it uses only as many digits as necessary.
It seems like Math.round is a better solution. But it is not! In some cases it will not round correctly:
Math.round(1.005 * 100)/100 // Returns 1 instead of expected 1.01!
toFixed() will also not round correctly in some cases (tested in Chrome v.55.0.2883.87)!
Examples:
parseFloat("1.555").toFixed(2); // Returns 1.55 instead of 1.56.
parseFloat("1.5550").toFixed(2); // Returns 1.55 instead of 1.56.
// However, it will return correct result if you round 1.5551.
parseFloat("1.5551").toFixed(2); // Returns 1.56 as expected.
1.3555.toFixed(3) // Returns 1.355 instead of expected 1.356.
// However, it will return correct result if you round 1.35551.
1.35551.toFixed(2); // Returns 1.36 as expected.
I guess, this is because 1.555 is actually something like float 1.55499994 behind the scenes.
Solution 1 is to use a script with required rounding algorithm, for example:
function roundNumber(num, scale) {
if(!("" + num).includes("e")) {
return +(Math.round(num + "e+" + scale) + "e-" + scale);
} else {
var arr = ("" + num).split("e");
var sig = ""
if(+arr[1] + scale > 0) {
sig = "+";
}
return +(Math.round(+arr[0] + "e" + sig + (+arr[1] + scale)) + "e-" + scale);
}
}
It is also at Plunker.
Note: This is not a universal solution for everyone. There are several different rounding algorithms. Your implementation can be different, and it depends on your requirements. See also Rounding.
Solution 2 is to avoid front end calculations and pull rounded values from the backend server.
Another possible solution, which is not a bulletproof either.
Math.round((num + Number.EPSILON) * 100) / 100
In some cases, when you round a number like 1.3549999999999998, it will return an incorrect result. It should be 1.35, but the result is 1.36.
I found this on MDN. Their way avoids the problem with 1.005 that was mentioned.
function roundToTwo(num) {
return +(Math.round(num + "e+2") + "e-2");
}
console.log('1.005 => ', roundToTwo(1.005));
console.log('10 => ', roundToTwo(10));
console.log('1.7777777 => ', roundToTwo(1.7777777));
console.log('9.1 => ', roundToTwo(9.1));
console.log('1234.5678 => ', roundToTwo(1234.5678));
MarkG's answer is the correct one. Here's a generic extension for any number of decimal places.
Number.prototype.round = function(places) {
return +(Math.round(this + "e+" + places) + "e-" + places);
}
Usage:
var n = 1.7777;
n.round(2); // 1.78
Unit test:
it.only('should round floats to 2 places', function() {
var cases = [
{ n: 10, e: 10, p:2 },
{ n: 1.7777, e: 1.78, p:2 },
{ n: 1.005, e: 1.01, p:2 },
{ n: 1.005, e: 1, p:0 },
{ n: 1.77777, e: 1.8, p:1 }
]
cases.forEach(function(testCase) {
var r = testCase.n.round(testCase.p);
assert.equal(r, testCase.e, 'didn\'t get right number');
});
})
You should use:
Math.round( num * 100 + Number.EPSILON ) / 100
No one seems to be aware of Number.EPSILON.
Also it's worth noting that this is not a JavaScript weirdness like some people stated.
That is simply the way floating point numbers works in a computer. Like 99% of programming languages, JavaScript doesn't have home made floating point numbers; it relies on the CPU/FPU for that. A computer uses binary, and in binary, there isn't any numbers like 0.1, but a mere binary approximation for that. Why? For the same reason than 1/3 cannot be written in decimal: its value is 0.33333333... with an infinity of threes.
Here come Number.EPSILON. That number is the difference between 1 and the next number existing in the double precision floating point numbers. That's it: There is no number between 1 and 1 + Number.EPSILON.
EDIT:
As asked in the comments, let's clarify one thing: adding Number.EPSILON is relevant only when the value to round is the result of an arithmetic operation, as it can swallow some floating point error delta.
It's not useful when the value comes from a direct source (e.g.: literal, user input or sensor).
EDIT (2019):
Like #maganap and some peoples have pointed out, it's best to add Number.EPSILON before multiplying:
Math.round( ( num + Number.EPSILON ) * 100 ) / 100
EDIT (december 2019):
Lately, I use a function similar to this one for comparing numbers epsilon-aware:
const ESPILON_RATE = 1 + Number.EPSILON ;
const ESPILON_ZERO = Number.MIN_VALUE ;
function epsilonEquals( a , b ) {
if ( Number.isNaN( a ) || Number.isNaN( b ) ) {
return false ;
}
if ( a === 0 || b === 0 ) {
return a <= b + EPSILON_ZERO && b <= a + EPSILON_ZERO ;
}
return a <= b * EPSILON_RATE && b <= a * EPSILON_RATE ;
}
My use-case is an assertion + data validation lib I'm developing for many years.
In fact, in the code I'm using ESPILON_RATE = 1 + 4 * Number.EPSILON and EPSILON_ZERO = 4 * Number.MIN_VALUE (four times the epsilon), because I want an equality checker loose enough for cumulating floating point error.
So far, it looks perfect for me.
I hope it will help.
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.
function naiveRound(num, decimalPlaces = 0) {
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)
// testing edge cases
console.log( naiveRound(1.005, 2) ); // 1 incorrect (should be 1.01)
console.log( naiveRound(2.175, 2) ); // 2.17 incorrect (should be 2.18)
console.log( naiveRound(5.015, 2) ); // 5.01 incorrect (should be 5.02)
In order to determine whether a rounding operation involves a midpoint value, the Round function multiplies the original value to be rounded by 10 ** n, where n is the desired number of fractional digits in the return value, and then determines whether the remaining fractional portion of the value is greater than or equal to .5. This "Exact Testing for Equality" with floating-point values are problematic because of the floating-point format's issues with binary representation and precision. This means that any fractional portion of a number that is slightly less than .5 (because of a loss of precision) will not be rounded upward.
In the previous example, 5.015 is a midpoint value if it is to be rounded to two decimal places, the value of 5.015 * 100 is actually 501.49999999999994. Because .49999999999994 is less than .5, it is rounded down to 501 and finally the result is 5.01.
Better implementations
Exponential notation
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.
/**
* Round half up ('round half towards positive infinity')
* Negative numbers round differently than positive numbers.
*/
function round(num, decimalPlaces = 0) {
num = Math.round(num + "e" + decimalPlaces);
return Number(num + "e" + -decimalPlaces);
}
// test rounding of half
console.log( round(0.5) ); // 1
console.log( round(-0.5) ); // 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
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 = 0) {
if (num < 0)
return -round(-num, decimalPlaces);
num = Math.round(num + "e" + decimalPlaces);
return Number(num + "e" + -decimalPlaces);
}
Approximate rounding
To correct the rounding problem shown in the previous naiveRound example, we can define a custom rounding function that performs a "nearly equal" test to determine whether a fractional value is sufficiently close to a midpoint value to be subject to midpoint rounding.
// round half away from zero
function round(num, decimalPlaces = 0) {
if (num < 0)
return -round(-num, decimalPlaces);
var p = Math.pow(10, decimalPlaces);
var n = num * p;
var f = n - Math.floor(n);
var e = Number.EPSILON * n;
// Determine whether this fraction is a midpoint value.
return (f >= .5 - e) ? Math.ceil(n) / p : Math.floor(n) / p;
}
// test rounding of half
console.log( round(0.5) ); // 1
console.log( round(-0.5) ); // -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
Number.EPSILON
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 product before rounding. This moves to the next representable float value, away from zero, thus it will offset the binary round-off error that may occur during the multiplication by 10 ** n.
/**
* 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 = 0) {
var p = Math.pow(10, decimalPlaces);
var n = (num * p) * (1 + Number.EPSILON);
return Math.round(n) / p;
}
// rounding of half
console.log( round(0.5) ); // 1
console.log( round(-0.5) ); // -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
After adding 1 ulp, the value of 5.015 * 100 which is 501.49999999999994 will be corrected to 501.50000000000006, this will rounded up to 502 and finally the result is 5.02.
Note that the size of a unit in last place ("ulp") is determined by (1) the magnitude of the number and (2) the relative machine epsilon (2^-52). Ulps are relatively larger at numbers with bigger magnitudes than they are at numbers with smaller magnitudes.
Double rounding
Here, we use the toPrecision() method to strip the floating-point round-off errors in the intermediate calculations. Simply, we round to 15 significant figures to strip the round-off error at the 16th significant digit. This technique to preround the result to significant digits is also used by PHP 7 round function.
The value of 5.015 * 100 which is 501.49999999999994 will be rounded first to 15 significant digits as 501.500000000000, then it will rounded up again to 502 and finally the result is 5.02.
// Round half away from zero
function round(num, decimalPlaces = 0) {
if (num < 0)
return -round(-num, decimalPlaces);
var p = Math.pow(10, decimalPlaces);
var n = (num * p).toPrecision(15);
return Math.round(n) / p;
}
// rounding of half
console.log( round(0.5) ); // 1
console.log( round(-0.5) ); // -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
Arbitrary-precision JavaScript library - decimal.js
// Round half away from zero
function round(num, decimalPlaces = 0) {
return new Decimal(num).toDecimalPlaces(decimalPlaces).toNumber();
}
// rounding of half
console.log( round(0.5) ); // 1
console.log( round(-0.5) ); // -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
<script src="https://cdnjs.cloudflare.com/ajax/libs/decimal.js/10.2.1/decimal.js" integrity="sha512-GKse2KVGCCMVBn4riigHjXE8j5hCxYLPXDw8AvcjUtrt+a9TbZFtIKGdArXwYOlZvdmkhQLWQ46ZE3Q1RIa7uQ==" crossorigin="anonymous"></script>
Solution 1: string in exponential notation
Inspired by the solution provided by KFish here: https://stackoverflow.com/a/55521592/4208440
A simple drop in solution that provides accurate decimal rounding, flooring, and ceiling to a specific number of decimal places without adding a whole library. It treats floats more like decimals by fixing the binary rounding issues to avoid unexpected results: for example, floor((0.1+0.7)*10) will return the expected result 8.
Numbers are rounded to a specific number of fractional digits. Specifying a negative precision will round to any number of places to the left of the decimal point.
// Solution 1
var DecimalPrecision = (function() {
if (Math.trunc === undefined) {
Math.trunc = function(v) {
return v < 0 ? Math.ceil(v) : Math.floor(v);
};
}
var decimalAdjust = function myself(type, num, decimalPlaces) {
if (type === 'round' && num < 0)
return -myself(type, -num, decimalPlaces);
var shift = function(value, exponent) {
value = (value + 'e').split('e');
return +(value[0] + 'e' + (+value[1] + (exponent || 0)));
};
var n = shift(num, +decimalPlaces);
return shift(Math[type](n), -decimalPlaces);
};
return {
// Decimal round (half away from zero)
round: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces);
},
// Decimal ceil
ceil: function(num, decimalPlaces) {
return decimalAdjust('ceil', num, decimalPlaces);
},
// Decimal floor
floor: function(num, decimalPlaces) {
return decimalAdjust('floor', num, decimalPlaces);
},
// Decimal trunc
trunc: function(num, decimalPlaces) {
return decimalAdjust('trunc', num, decimalPlaces);
},
// Format using fixed-point notation
toFixed: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces).toFixed(decimalPlaces);
}
};
})();
// test rounding of half
console.log(DecimalPrecision.round(0.5)); // 1
console.log(DecimalPrecision.round(-0.5)); // -1
// testing very small numbers
console.log(DecimalPrecision.ceil(1e-8, 2) === 0.01);
console.log(DecimalPrecision.floor(1e-8, 2) === 0);
// testing simple cases
console.log(DecimalPrecision.round(5.12, 1) === 5.1);
console.log(DecimalPrecision.round(-5.12, 1) === -5.1);
console.log(DecimalPrecision.ceil(5.12, 1) === 5.2);
console.log(DecimalPrecision.ceil(-5.12, 1) === -5.1);
console.log(DecimalPrecision.floor(5.12, 1) === 5.1);
console.log(DecimalPrecision.floor(-5.12, 1) === -5.2);
console.log(DecimalPrecision.trunc(5.12, 1) === 5.1);
console.log(DecimalPrecision.trunc(-5.12, 1) === -5.1);
// testing edge cases for round
console.log(DecimalPrecision.round(1.005, 2) === 1.01);
console.log(DecimalPrecision.round(39.425, 2) === 39.43);
console.log(DecimalPrecision.round(-1.005, 2) === -1.01);
console.log(DecimalPrecision.round(-39.425, 2) === -39.43);
// testing edge cases for ceil
console.log(DecimalPrecision.ceil(9.13, 2) === 9.13);
console.log(DecimalPrecision.ceil(65.18, 2) === 65.18);
console.log(DecimalPrecision.ceil(-2.26, 2) === -2.26);
console.log(DecimalPrecision.ceil(-18.15, 2) === -18.15);
// testing edge cases for floor
console.log(DecimalPrecision.floor(2.26, 2) === 2.26);
console.log(DecimalPrecision.floor(18.15, 2) === 18.15);
console.log(DecimalPrecision.floor(-9.13, 2) === -9.13);
console.log(DecimalPrecision.floor(-65.18, 2) === -65.18);
// testing edge cases for trunc
console.log(DecimalPrecision.trunc(2.26, 2) === 2.26);
console.log(DecimalPrecision.trunc(18.15, 2) === 18.15);
console.log(DecimalPrecision.trunc(-2.26, 2) === -2.26);
console.log(DecimalPrecision.trunc(-18.15, 2) === -18.15);
// testing round to tens and hundreds
console.log(DecimalPrecision.round(1262.48, -1) === 1260);
console.log(DecimalPrecision.round(1262.48, -2) === 1300);
// testing toFixed()
console.log(DecimalPrecision.toFixed(1.005, 2) === "1.01");
Solution 2: purely mathematical (Number.EPSILON)
This solution avoids any string conversion / manipulation of any kind for performance reasons.
// Solution 2
var DecimalPrecision2 = (function() {
if (Number.EPSILON === undefined) {
Number.EPSILON = Math.pow(2, -52);
}
if (Math.sign === undefined) {
Math.sign = function(x) {
return ((x > 0) - (x < 0)) || +x;
};
}
return {
// Decimal round (half away from zero)
round: function(num, decimalPlaces) {
var p = Math.pow(10, decimalPlaces || 0);
var n = (num * p) * (1 + Number.EPSILON);
return Math.round(n) / p;
},
// Decimal ceil
ceil: function(num, decimalPlaces) {
var p = Math.pow(10, decimalPlaces || 0);
var n = (num * p) * (1 - Math.sign(num) * Number.EPSILON);
return Math.ceil(n) / p;
},
// Decimal floor
floor: function(num, decimalPlaces) {
var p = Math.pow(10, decimalPlaces || 0);
var n = (num * p) * (1 + Math.sign(num) * Number.EPSILON);
return Math.floor(n) / p;
},
// Decimal trunc
trunc: function(num, decimalPlaces) {
return (num < 0 ? this.ceil : this.floor)(num, decimalPlaces);
},
// Format using fixed-point notation
toFixed: function(num, decimalPlaces) {
return this.round(num, decimalPlaces).toFixed(decimalPlaces);
}
};
})();
// test rounding of half
console.log(DecimalPrecision2.round(0.5)); // 1
console.log(DecimalPrecision2.round(-0.5)); // -1
// testing very small numbers
console.log(DecimalPrecision2.ceil(1e-8, 2) === 0.01);
console.log(DecimalPrecision2.floor(1e-8, 2) === 0);
// testing simple cases
console.log(DecimalPrecision2.round(5.12, 1) === 5.1);
console.log(DecimalPrecision2.round(-5.12, 1) === -5.1);
console.log(DecimalPrecision2.ceil(5.12, 1) === 5.2);
console.log(DecimalPrecision2.ceil(-5.12, 1) === -5.1);
console.log(DecimalPrecision2.floor(5.12, 1) === 5.1);
console.log(DecimalPrecision2.floor(-5.12, 1) === -5.2);
console.log(DecimalPrecision2.trunc(5.12, 1) === 5.1);
console.log(DecimalPrecision2.trunc(-5.12, 1) === -5.1);
// testing edge cases for round
console.log(DecimalPrecision2.round(1.005, 2) === 1.01);
console.log(DecimalPrecision2.round(39.425, 2) === 39.43);
console.log(DecimalPrecision2.round(-1.005, 2) === -1.01);
console.log(DecimalPrecision2.round(-39.425, 2) === -39.43);
// testing edge cases for ceil
console.log(DecimalPrecision2.ceil(9.13, 2) === 9.13);
console.log(DecimalPrecision2.ceil(65.18, 2) === 65.18);
console.log(DecimalPrecision2.ceil(-2.26, 2) === -2.26);
console.log(DecimalPrecision2.ceil(-18.15, 2) === -18.15);
// testing edge cases for floor
console.log(DecimalPrecision2.floor(2.26, 2) === 2.26);
console.log(DecimalPrecision2.floor(18.15, 2) === 18.15);
console.log(DecimalPrecision2.floor(-9.13, 2) === -9.13);
console.log(DecimalPrecision2.floor(-65.18, 2) === -65.18);
// testing edge cases for trunc
console.log(DecimalPrecision2.trunc(2.26, 2) === 2.26);
console.log(DecimalPrecision2.trunc(18.15, 2) === 18.15);
console.log(DecimalPrecision2.trunc(-2.26, 2) === -2.26);
console.log(DecimalPrecision2.trunc(-18.15, 2) === -18.15);
// testing round to tens and hundreds
console.log(DecimalPrecision2.round(1262.48, -1) === 1260);
console.log(DecimalPrecision2.round(1262.48, -2) === 1300);
// testing toFixed()
console.log(DecimalPrecision2.toFixed(1.005, 2) === "1.01");
Solution 3: double rounding
This solution uses the toPrecision() method to strip the floating-point round-off errors.
// Solution 3
var DecimalPrecision3 = (function() {
if (Math.trunc === undefined) {
Math.trunc = function(v) {
return v < 0 ? Math.ceil(v) : Math.floor(v);
};
}
var powers = [
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7,
1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22
];
var intpow10 = function(power) {
/* Not in lookup table */
if (power < 0 || power > 22) {
return Math.pow(10, power);
}
return powers[power];
};
// Eliminate binary floating-point inaccuracies.
var stripError = function(num) {
if (Number.isInteger(num))
return num;
return parseFloat(num.toPrecision(15));
};
var decimalAdjust = function myself(type, num, decimalPlaces) {
if (type === 'round' && num < 0)
return -myself(type, -num, decimalPlaces);
var p = intpow10(decimalPlaces || 0);
var n = stripError(num * p);
return Math[type](n) / p;
};
return {
// Decimal round (half away from zero)
round: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces);
},
// Decimal ceil
ceil: function(num, decimalPlaces) {
return decimalAdjust('ceil', num, decimalPlaces);
},
// Decimal floor
floor: function(num, decimalPlaces) {
return decimalAdjust('floor', num, decimalPlaces);
},
// Decimal trunc
trunc: function(num, decimalPlaces) {
return decimalAdjust('trunc', num, decimalPlaces);
},
// Format using fixed-point notation
toFixed: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces).toFixed(decimalPlaces);
}
};
})();
// test rounding of half
console.log(DecimalPrecision3.round(0.5)); // 1
console.log(DecimalPrecision3.round(-0.5)); // -1
// testing very small numbers
console.log(DecimalPrecision3.ceil(1e-8, 2) === 0.01);
console.log(DecimalPrecision3.floor(1e-8, 2) === 0);
// testing simple cases
console.log(DecimalPrecision3.round(5.12, 1) === 5.1);
console.log(DecimalPrecision3.round(-5.12, 1) === -5.1);
console.log(DecimalPrecision3.ceil(5.12, 1) === 5.2);
console.log(DecimalPrecision3.ceil(-5.12, 1) === -5.1);
console.log(DecimalPrecision3.floor(5.12, 1) === 5.1);
console.log(DecimalPrecision3.floor(-5.12, 1) === -5.2);
console.log(DecimalPrecision3.trunc(5.12, 1) === 5.1);
console.log(DecimalPrecision3.trunc(-5.12, 1) === -5.1);
// testing edge cases for round
console.log(DecimalPrecision3.round(1.005, 2) === 1.01);
console.log(DecimalPrecision3.round(39.425, 2) === 39.43);
console.log(DecimalPrecision3.round(-1.005, 2) === -1.01);
console.log(DecimalPrecision3.round(-39.425, 2) === -39.43);
// testing edge cases for ceil
console.log(DecimalPrecision3.ceil(9.13, 2) === 9.13);
console.log(DecimalPrecision3.ceil(65.18, 2) === 65.18);
console.log(DecimalPrecision3.ceil(-2.26, 2) === -2.26);
console.log(DecimalPrecision3.ceil(-18.15, 2) === -18.15);
// testing edge cases for floor
console.log(DecimalPrecision3.floor(2.26, 2) === 2.26);
console.log(DecimalPrecision3.floor(18.15, 2) === 18.15);
console.log(DecimalPrecision3.floor(-9.13, 2) === -9.13);
console.log(DecimalPrecision3.floor(-65.18, 2) === -65.18);
// testing edge cases for trunc
console.log(DecimalPrecision3.trunc(2.26, 2) === 2.26);
console.log(DecimalPrecision3.trunc(18.15, 2) === 18.15);
console.log(DecimalPrecision3.trunc(-2.26, 2) === -2.26);
console.log(DecimalPrecision3.trunc(-18.15, 2) === -18.15);
// testing round to tens and hundreds
console.log(DecimalPrecision3.round(1262.48, -1) === 1260);
console.log(DecimalPrecision3.round(1262.48, -2) === 1300);
// testing toFixed()
console.log(DecimalPrecision3.toFixed(1.005, 2) === "1.01");
Solution 4: double rounding v2
This solution is just like Solution 3, however it uses a custom toPrecision() function.
// Solution 4
var DecimalPrecision4 = (function() {
if (Math.trunc === undefined) {
Math.trunc = function(v) {
return v < 0 ? Math.ceil(v) : Math.floor(v);
};
}
var powers = [
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7,
1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22
];
var intpow10 = function(power) {
/* Not in lookup table */
if (power < 0 || power > 22) {
return Math.pow(10, power);
}
return powers[power];
};
var toPrecision = function(num, significantDigits) {
// Return early for ±0, NaN and Infinity.
if (!num || !Number.isFinite(num))
return num;
// Compute shift of the decimal point (sf - leftSidedDigits).
var shift = significantDigits - 1 - Math.floor(Math.log10(Math.abs(num)));
// Return if rounding to the same or higher precision.
var decimalPlaces = 0;
for (var p = 1; num != Math.round(num * p) / p; p *= 10) decimalPlaces++;
if (shift >= decimalPlaces)
return num;
// Round to "shift" fractional digits
var scale = intpow10(Math.abs(shift));
return shift > 0 ?
Math.round(num * scale) / scale :
Math.round(num / scale) * scale;
};
// Eliminate binary floating-point inaccuracies.
var stripError = function(num) {
if (Number.isInteger(num))
return num;
return toPrecision(num, 15);
};
var decimalAdjust = function myself(type, num, decimalPlaces) {
if (type === 'round' && num < 0)
return -myself(type, -num, decimalPlaces);
var p = intpow10(decimalPlaces || 0);
var n = stripError(num * p);
return Math[type](n) / p;
};
return {
// Decimal round (half away from zero)
round: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces);
},
// Decimal ceil
ceil: function(num, decimalPlaces) {
return decimalAdjust('ceil', num, decimalPlaces);
},
// Decimal floor
floor: function(num, decimalPlaces) {
return decimalAdjust('floor', num, decimalPlaces);
},
// Decimal trunc
trunc: function(num, decimalPlaces) {
return decimalAdjust('trunc', num, decimalPlaces);
},
// Format using fixed-point notation
toFixed: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces).toFixed(decimalPlaces);
}
};
})();
// test rounding of half
console.log(DecimalPrecision4.round(0.5)); // 1
console.log(DecimalPrecision4.round(-0.5)); // -1
// testing very small numbers
console.log(DecimalPrecision4.ceil(1e-8, 2) === 0.01);
console.log(DecimalPrecision4.floor(1e-8, 2) === 0);
// testing simple cases
console.log(DecimalPrecision4.round(5.12, 1) === 5.1);
console.log(DecimalPrecision4.round(-5.12, 1) === -5.1);
console.log(DecimalPrecision4.ceil(5.12, 1) === 5.2);
console.log(DecimalPrecision4.ceil(-5.12, 1) === -5.1);
console.log(DecimalPrecision4.floor(5.12, 1) === 5.1);
console.log(DecimalPrecision4.floor(-5.12, 1) === -5.2);
console.log(DecimalPrecision4.trunc(5.12, 1) === 5.1);
console.log(DecimalPrecision4.trunc(-5.12, 1) === -5.1);
// testing edge cases for round
console.log(DecimalPrecision4.round(1.005, 2) === 1.01);
console.log(DecimalPrecision4.round(39.425, 2) === 39.43);
console.log(DecimalPrecision4.round(-1.005, 2) === -1.01);
console.log(DecimalPrecision4.round(-39.425, 2) === -39.43);
// testing edge cases for ceil
console.log(DecimalPrecision4.ceil(9.13, 2) === 9.13);
console.log(DecimalPrecision4.ceil(65.18, 2) === 65.18);
console.log(DecimalPrecision4.ceil(-2.26, 2) === -2.26);
console.log(DecimalPrecision4.ceil(-18.15, 2) === -18.15);
// testing edge cases for floor
console.log(DecimalPrecision4.floor(2.26, 2) === 2.26);
console.log(DecimalPrecision4.floor(18.15, 2) === 18.15);
console.log(DecimalPrecision4.floor(-9.13, 2) === -9.13);
console.log(DecimalPrecision4.floor(-65.18, 2) === -65.18);
// testing edge cases for trunc
console.log(DecimalPrecision4.trunc(2.26, 2) === 2.26);
console.log(DecimalPrecision4.trunc(18.15, 2) === 18.15);
console.log(DecimalPrecision4.trunc(-2.26, 2) === -2.26);
console.log(DecimalPrecision4.trunc(-18.15, 2) === -18.15);
// testing round to tens and hundreds
console.log(DecimalPrecision4.round(1262.48, -1) === 1260);
console.log(DecimalPrecision4.round(1262.48, -2) === 1300);
// testing toFixed()
console.log(DecimalPrecision4.toFixed(1.005, 2) === "1.01");
Benchmarks
http://jsbench.github.io/#31ec3a8b3d22bd840f8e6822e681a3ac
Here is a benchmark comparing the operations per second in the solutions above on Chrome 109.0.0.0. Rounding functions using the Number.EPSILON is at least 10x-20x faster. Obviously all browsers differ, so your mileage may vary.
(Note: More is better)
Thanks #Mike for adding a screenshot of the benchmark.
This question is complicated.
Suppose we have a function, roundTo2DP(num), that takes a float as an argument and returns a value rounded to 2 decimal places. What should each of these expressions evaluate to?
roundTo2DP(0.014999999999999999)
roundTo2DP(0.0150000000000000001)
roundTo2DP(0.015)
The 'obvious' answer is that the first example should round to 0.01 (because it's closer to 0.01 than to 0.02) while the other two should round to 0.02 (because 0.0150000000000000001 is closer to 0.02 than to 0.01, and because 0.015 is exactly halfway between them and there is a mathematical convention that such numbers get rounded up).
The catch, which you may have guessed, is that roundTo2DP cannot possibly be implemented to give those obvious answers, because all three numbers passed to it are the same number. IEEE 754 binary floating point numbers (the kind used by JavaScript) can't exactly represent most non-integer numbers, and so all three numeric literals above get rounded to a nearby valid floating point number. This number, as it happens, is exactly
0.01499999999999999944488848768742172978818416595458984375
which is closer to 0.01 than to 0.02.
You can see that all three numbers are the same at your browser console, Node shell, or other JavaScript interpreter. Just compare them:
> 0.014999999999999999 === 0.0150000000000000001
true
So when I write m = 0.0150000000000000001, the exact value of m that I end up with is closer to 0.01 than it is to 0.02. And yet, if I convert m to a String...
> var m = 0.0150000000000000001;
> console.log(String(m));
0.015
> var m = 0.014999999999999999;
> console.log(String(m));
0.015
... I get 0.015, which should round to 0.02, and which is noticeably not the 56-decimal-place number I earlier said that all of these numbers were exactly equal to. So what dark magic is this?
The answer can be found in the ECMAScript specification, in section 7.1.12.1: ToString applied to the Number type. Here the rules for converting some Number m to a String are laid down. The key part is point 5, in which an integer s is generated whose digits will be used in the String representation of m:
let n, k, and s be integers such that k ≥ 1, 10k-1 ≤ s < 10k, the Number value for s × 10n-k is m, and k is as small as possible. Note that k is the number of digits in the decimal representation of s, that s is not divisible by 10, and that the least significant digit of s is not necessarily uniquely determined by these criteria.
The key part here is the requirement that "k is as small as possible". What that requirement amounts to is a requirement that, given a Number m, the value of String(m) must have the least possible number of digits while still satisfying the requirement that Number(String(m)) === m. Since we already know that 0.015 === 0.0150000000000000001, it's now clear why String(0.0150000000000000001) === '0.015' must be true.
Of course, none of this discussion has directly answered what roundTo2DP(m) should return. If m's exact value is 0.01499999999999999944488848768742172978818416595458984375, but its String representation is '0.015', then what is the correct answer - mathematically, practically, philosophically, or whatever - when we round it to two decimal places?
There is no single correct answer to this. It depends upon your use case. You probably want to respect the String representation and round upwards when:
The value being represented is inherently discrete, e.g. an amount of currency in a 3-decimal-place currency like dinars. In this case, the true value of a Number like 0.015 is 0.015, and the 0.0149999999... representation that it gets in binary floating point is a rounding error. (Of course, many will argue, reasonably, that you should use a decimal library for handling such values and never represent them as binary floating point Numbers in the first place.)
The value was typed by a user. In this case, again, the exact decimal number entered is more 'true' than the nearest binary floating point representation.
On the other hand, you probably want to respect the binary floating point value and round downwards when your value is from an inherently continuous scale - for instance, if it's a reading from a sensor.
These two approaches require different code. To respect the String representation of the Number, we can (with quite a bit of reasonably subtle code) implement our own rounding that acts directly on the String representation, digit by digit, using the same algorithm you would've used in school when you were taught how to round numbers. Below is an example which respects the OP's requirement of representing the number to 2 decimal places "only when necessary" by stripping trailing zeroes after the decimal point; you may, of course, need to tweak it to your precise needs.
/**
* Converts num to a decimal string (if it isn't one already) and then rounds it
* to at most dp decimal places.
*
* For explanation of why you'd want to perform rounding operations on a String
* rather than a Number, see http://stackoverflow.com/a/38676273/1709587
*
* #param {(number|string)} num
* #param {number} dp
* #return {string}
*/
function roundStringNumberWithoutTrailingZeroes (num, dp) {
if (arguments.length != 2) throw new Error("2 arguments required");
num = String(num);
if (num.indexOf('e+') != -1) {
// Can't round numbers this large because their string representation
// contains an exponent, like 9.99e+37
throw new Error("num too large");
}
if (num.indexOf('.') == -1) {
// Nothing to do
return num;
}
if (num[0] == '-') {
return "-" + roundStringNumberWithoutTrailingZeroes(num.slice(1), dp)
}
var parts = num.split('.'),
beforePoint = parts[0],
afterPoint = parts[1],
shouldRoundUp = afterPoint[dp] >= 5,
finalNumber;
afterPoint = afterPoint.slice(0, dp);
if (!shouldRoundUp) {
finalNumber = beforePoint + '.' + afterPoint;
} else if (/^9+$/.test(afterPoint)) {
// If we need to round up a number like 1.9999, increment the integer
// before the decimal point and discard the fractional part.
// We want to do this while still avoiding converting the whole
// beforePart to a Number (since that could cause loss of precision if
// beforePart is bigger than Number.MAX_SAFE_INTEGER), so the logic for
// this is once again kinda complicated.
// Note we can (and want to) use early returns here because the
// zero-stripping logic at the end of
// roundStringNumberWithoutTrailingZeroes does NOT apply here, since
// the result is a whole number.
if (/^9+$/.test(beforePoint)) {
return "1" + beforePoint.replaceAll("9", "0")
}
// Starting from the last digit, increment digits until we find one
// that is not 9, then stop
var i = beforePoint.length - 1;
while (true) {
if (beforePoint[i] == '9') {
beforePoint = beforePoint.substr(0, i) +
'0' +
beforePoint.substr(i+1);
i--;
} else {
beforePoint = beforePoint.substr(0, i) +
(Number(beforePoint[i]) + 1) +
beforePoint.substr(i+1);
break;
}
}
return beforePoint
} else {
// Starting from the last digit, increment digits until we find one
// that is not 9, then stop
var i = dp-1;
while (true) {
if (afterPoint[i] == '9') {
afterPoint = afterPoint.substr(0, i) +
'0' +
afterPoint.substr(i+1);
i--;
} else {
afterPoint = afterPoint.substr(0, i) +
(Number(afterPoint[i]) + 1) +
afterPoint.substr(i+1);
break;
}
}
finalNumber = beforePoint + '.' + afterPoint;
}
// Remove trailing zeroes from fractional part before returning
return finalNumber.replace(/0+$/, '')
}
Example usage:
> roundStringNumberWithoutTrailingZeroes(1.6, 2)
'1.6'
> roundStringNumberWithoutTrailingZeroes(10000, 2)
'10000'
> roundStringNumberWithoutTrailingZeroes(0.015, 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes('0.015000', 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes(1, 1)
'1'
> roundStringNumberWithoutTrailingZeroes('0.015', 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes(0.01499999999999999944488848768742172978818416595458984375, 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes('0.01499999999999999944488848768742172978818416595458984375', 2)
'0.01'
> roundStringNumberWithoutTrailingZeroes('16.996', 2)
'17'
The function above is probably what you want to use to avoid users ever witnessing numbers that they have entered being rounded wrongly.
(As an alternative, you could also try the round10 library which provides a similarly-behaving function with a wildly different implementation.)
But what if you have the second kind of Number - a value taken from a continuous scale, where there's no reason to think that approximate decimal representations with fewer decimal places are more accurate than those with more? In that case, we don't want to respect the String representation, because that representation (as explained in the spec) is already sort-of-rounded; we don't want to make the mistake of saying "0.014999999...375 rounds up to 0.015, which rounds up to 0.02, so 0.014999999...375 rounds up to 0.02".
Here we can simply use the built-in toFixed method. Note that by calling Number() on the String returned by toFixed, we get a Number whose String representation has no trailing zeroes (thanks to the way JavaScript computes the String representation of a Number, discussed earlier in this answer).
/**
* Takes a float and rounds it to at most dp decimal places. For example
*
* roundFloatNumberWithoutTrailingZeroes(1.2345, 3)
*
* returns 1.234
*
* Note that since this treats the value passed to it as a floating point
* number, it will have counterintuitive results in some cases. For instance,
*
* roundFloatNumberWithoutTrailingZeroes(0.015, 2)
*
* gives 0.01 where 0.02 might be expected. For an explanation of why, see
* http://stackoverflow.com/a/38676273/1709587. You may want to consider using the
* roundStringNumberWithoutTrailingZeroes function there instead.
*
* #param {number} num
* #param {number} dp
* #return {number}
*/
function roundFloatNumberWithoutTrailingZeroes (num, dp) {
var numToFixedDp = Number(num).toFixed(dp);
return Number(numToFixedDp);
}
Consider .toFixed() and .toPrecision():
http://www.javascriptkit.com/javatutors/formatnumber.shtml
One can use .toFixed(NumberOfDecimalPlaces).
var str = 10.234.toFixed(2); // => '10.23'
var number = Number(str); // => 10.23
Here is a simple way to do it:
Math.round(value * 100) / 100
You might want to go ahead and make a separate function to do it for you though:
function roundToTwo(value) {
return(Math.round(value * 100) / 100);
}
Then you would simply pass in the value.
You could enhance it to round to any arbitrary number of decimals by adding a second parameter.
function myRound(value, places) {
var multiplier = Math.pow(10, places);
return (Math.round(value * multiplier) / multiplier);
}
A precise rounding method. Source: Mozilla
(function(){
/**
* Decimal adjustment of a number.
*
* #param {String} type The type of adjustment.
* #param {Number} value The number.
* #param {Integer} exp The exponent (the 10 logarithm of the adjustment base).
* #returns {Number} The adjusted value.
*/
function decimalAdjust(type, value, exp) {
// If the exp is undefined or zero...
if (typeof exp === 'undefined' || +exp === 0) {
return Math[type](value);
}
value = +value;
exp = +exp;
// If the value is not a number or the exp is not an integer...
if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
return NaN;
}
// Shift
value = value.toString().split('e');
value = Math[type](+(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));
}
// Decimal round
if (!Math.round10) {
Math.round10 = function(value, exp) {
return decimalAdjust('round', value, exp);
};
}
// Decimal floor
if (!Math.floor10) {
Math.floor10 = function(value, exp) {
return decimalAdjust('floor', value, exp);
};
}
// Decimal ceil
if (!Math.ceil10) {
Math.ceil10 = function(value, exp) {
return decimalAdjust('ceil', value, exp);
};
}
})();
Examples:
// Round
Math.round10(55.55, -1); // 55.6
Math.round10(55.549, -1); // 55.5
Math.round10(55, 1); // 60
Math.round10(54.9, 1); // 50
Math.round10(-55.55, -1); // -55.5
Math.round10(-55.551, -1); // -55.6
Math.round10(-55, 1); // -50
Math.round10(-55.1, 1); // -60
Math.round10(1.005, -2); // 1.01 -- compare this with Math.round(1.005*100)/100 above
// Floor
Math.floor10(55.59, -1); // 55.5
Math.floor10(59, 1); // 50
Math.floor10(-55.51, -1); // -55.6
Math.floor10(-51, 1); // -60
// Ceil
Math.ceil10(55.51, -1); // 55.6
Math.ceil10(51, 1); // 60
Math.ceil10(-55.59, -1); // -55.5
Math.ceil10(-59, 1); // -50
None of the answers found here is correct. stinkycheeseman asked to round up, but you all rounded the number.
To round up, use this:
Math.ceil(num * 100)/100;
This may help you:
var result = Math.round(input*100)/100;
For more information, you can have a look at Math.round(num) vs num.toFixed(0) and browser inconsistencies
For me Math.round() was not giving correct answer. I found toFixed(2) works better.
Below are examples of both:
console.log(Math.round(43000 / 80000) * 100); // wrong answer
console.log(((43000 / 80000) * 100).toFixed(2)); // correct answer
Use this function Number(x).toFixed(2);
If you are using the Lodash library, you can use the round method of Lodash like following.
_.round(number, precision)
For example:
_.round(1.7777777, 2) = 1.78
+(10).toFixed(2); // = 10
+(10.12345).toFixed(2); // = 10.12
(10).toFixed(2); // = 10.00
(10.12345).toFixed(2); // = 10.12
Try this lightweight solution:
function round(x, digits){
return parseFloat(x.toFixed(digits))
}
round(1.222, 2);
// 1.22
round(1.222, 10);
// 1.222
There are a couple of ways to do that. For people like me, Lodash's variant
function round(number, precision) {
var pair = (number + 'e').split('e')
var value = Math.round(pair[0] + 'e' + (+pair[1] + precision))
pair = (value + 'e').split('e')
return +(pair[0] + 'e' + (+pair[1] - precision))
}
Usage:
round(0.015, 2) // 0.02
round(1.005, 2) // 1.01
If your project uses jQuery or Lodash, you can also find the proper round method in the libraries.
2017
Just use native code .toFixed()
number = 1.2345;
number.toFixed(2) // "1.23"
If you need to be strict and add digits just if needed it can use replace
number = 1; // "1"
number.toFixed(5).replace(/\.?0*$/g,'');
Another simple solution (without writing any function) may to use toFixed() and then convert to float again:
For example:
var objNumber = 1201203.1256546456;
objNumber = parseFloat(objNumber.toFixed(2))
Since ES6 there is a 'proper' way (without overriding statics and creating workarounds) to do this by using toPrecision
var x = 1.49999999999;
console.log(x.toPrecision(4));
console.log(x.toPrecision(3));
console.log(x.toPrecision(2));
var y = Math.PI;
console.log(y.toPrecision(6));
console.log(y.toPrecision(5));
console.log(y.toPrecision(4));
var z = 222.987654
console.log(z.toPrecision(6));
console.log(z.toPrecision(5));
console.log(z.toPrecision(4));
then you can just parseFloat and zeroes will 'go away'.
console.log(parseFloat((1.4999).toPrecision(3)));
console.log(parseFloat((1.005).toPrecision(3)));
console.log(parseFloat((1.0051).toPrecision(3)));
It doesn't solve the '1.005 rounding problem' though - since it is intrinsic to how float fractions are being processed.
console.log(1.005 - 0.005);
If you are open to libraries you can use bignumber.js
console.log(1.005 - 0.005);
console.log(new BigNumber(1.005).minus(0.005));
console.log(new BigNumber(1.005).round(4));
console.log(new BigNumber(1.005).round(3));
console.log(new BigNumber(1.005).round(2));
console.log(new BigNumber(1.005).round(1));
<script src="https://cdnjs.cloudflare.com/ajax/libs/bignumber.js/2.3.0/bignumber.min.js"></script>
The easiest approach would be to use toFixed and then strip trailing zeros using the Number function:
const number = 15.5;
Number(number.toFixed(2)); // 15.5
const number = 1.7777777;
Number(number.toFixed(2)); // 1.78
One way to achieve such a rounding only if necessary is to use Number.prototype.toLocaleString():
myNumber.toLocaleString('en', {maximumFractionDigits:2, useGrouping:false})
This will provide exactly the output you expect, but as strings. You can still convert those back to numbers if that's not the data type you expect.
MarkG and Lavamantis offered a much better solution than the one that has been accepted. It's a shame they don't get more upvotes!
Here is the function I use to solve the floating point decimals issues also based on MDN. It is even more generic (but less concise) than Lavamantis's solution:
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));
}
Use it with:
round(10.8034, 2); // Returns 10.8
round(1.275, 2); // Returns 1.28
round(1.27499, 2); // Returns 1.27
round(1.2345678e+2, 2); // Returns 123.46
Compared to Lavamantis's solution, we can do...
round(1234.5678, -2); // Returns 1200
round("123.45"); // Returns 123
It may work for you,
Math.round(num * 100)/100;
to know the difference between toFixed and round. You can have a look at Math.round(num) vs num.toFixed(0) and browser inconsistencies.
Keep the type as integer for later sorting or other arithmetic operations:
Math.round(1.7777777 * 100)/100
1.78
// Round up!
Math.ceil(1.7777777 * 100)/100
1.78
// Round down!
Math.floor(1.7777777 * 100)/100
1.77
Or convert to a string:
(1.7777777).toFixed(2)
"1.77"
This is the simplest, more elegant solution (and I am the best of the world;):
function roundToX(num, X) {
return +(Math.round(num + "e+"+X) + "e-"+X);
}
//roundToX(66.66666666,2) => 66.67
//roundToX(10,2) => 10
//roundToX(10.904,2) => 10.9
Modern syntax alternative with fallback values
const roundToX = (num = 0, X = 20) => +(Math.round(num + `e${X}`) + `e-${X}`)
var roundUpto = function(number, upto){
return Number(number.toFixed(upto));
}
roundUpto(0.1464676, 2);
toFixed(2): Here 2 is the number of digits up to which we want to round this number.
See AmrAli's answer for a more thorough run through and performance breakdown of all the various adaptations of this solution.
var DecimalPrecision = (function(){
if (Number.EPSILON === undefined) {
Number.EPSILON = Math.pow(2, -52);
}
if(Number.isInteger === undefined){
Number.isInteger = function(value) {
return typeof value === 'number' &&
isFinite(value) &&
Math.floor(value) === value;
};
}
this.isRound = function(n,p){
let l = n.toString().split('.')[1].length;
return (p >= l);
}
this.round = function(n, p=2){
if(Number.isInteger(n) || this.isRound(n,p))
return n;
let r = 0.5 * Number.EPSILON * n;
let o = 1; while(p-- > 0) o *= 10;
if(n<0)
o *= -1;
return Math.round((n + r) * o) / o;
}
this.ceil = function(n, p=2){
if(Number.isInteger(n) || this.isRound(n,p))
return n;
let r = 0.5 * Number.EPSILON * n;
let o = 1; while(p-- > 0) o *= 10;
return Math.ceil((n + r) * o) / o;
}
this.floor = function(n, p=2){
if(Number.isInteger(n) || this.isRound(n,p))
return n;
let r = 0.5 * Number.EPSILON * n;
let o = 1; while(p-- > 0) o *= 10;
return Math.floor((n + r) * o) / o;
}
return this;
})();
console.log(DecimalPrecision.round(1.005));
console.log(DecimalPrecision.ceil(1.005));
console.log(DecimalPrecision.floor(1.005));
console.log(DecimalPrecision.round(1.0049999));
console.log(DecimalPrecision.ceil(1.0049999));
console.log(DecimalPrecision.floor(1.0049999));
console.log(DecimalPrecision.round(2.175495134384,7));
console.log(DecimalPrecision.round(2.1753543549,8));
console.log(DecimalPrecision.round(2.1755465135353,4));
console.log(DecimalPrecision.ceil(17,4));
console.log(DecimalPrecision.ceil(17.1,4));
console.log(DecimalPrecision.ceil(17.1,15));
in JavaScript, the typical way to round a number to N decimal places is something like:
function roundNumber(num, dec) {
return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
}
function roundNumber(num, dec) {
return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
}
console.log(roundNumber(0.1 + 0.2, 2));
console.log(roundNumber(2.1234, 2));
However this approach will round to a maximum of N decimal places while I want to always round to N decimal places. For example "2.0" would be rounded to "2".
Any ideas?
I think that there is a more simple approach to all given here, and is the method Number.toFixed() already implemented in JavaScript.
simply write:
var myNumber = 2;
myNumber.toFixed(2); //returns "2.00"
myNumber.toFixed(1); //returns "2.0"
etc...
I found a way. This is Christoph's code with a fix:
function toFixed(value, precision) {
var precision = precision || 0,
power = Math.pow(10, precision),
absValue = Math.abs(Math.round(value * power)),
result = (value < 0 ? '-' : '') + String(Math.floor(absValue / power));
if (precision > 0) {
var fraction = String(absValue % power),
padding = new Array(Math.max(precision - fraction.length, 0) + 1).join('0');
result += '.' + padding + fraction;
}
return result;
}
Read the details of repeating a character using an array constructor here if you are curious as to why I added the "+ 1".
That's not a rounding ploblem, that is a display problem. A number doesn't contain information about significant digits; the value 2 is the same as 2.0000000000000. It's when you turn the rounded value into a string that you have make it display a certain number of digits.
You could just add zeroes after the number, something like:
var s = number.toString();
if (s.indexOf('.') == -1) s += '.';
while (s.length < s.indexOf('.') + 4) s += '0';
(Note that this assumes that the regional settings of the client uses period as decimal separator, the code needs some more work to function for other settings.)
There's always a better way for doing things. Use toPrecision -
var number = 51.93999999999761;
I would like to get four digits precision: 51.94
just do:
number.toPrecision(4);
the result will be: 51.94
This works for rounding to N digits (if you just want to truncate to N digits remove the Math.round call and use the Math.trunc one):
function roundN(value, digits) {
var tenToN = 10 ** digits;
return /*Math.trunc*/(Math.round(value * tenToN)) / tenToN;
}
Had to resort to such logic at Java in the past when I was authoring data manipulation E-Slate components. That is since I had found out that adding 0.1 many times to 0 you'd end up with some unexpectedly long decimal part (this is due to floating point arithmetics).
A user comment at Format number to always show 2 decimal places calls this technique scaling.
Some mention there are cases that don't round as expected and at http://www.jacklmoore.com/notes/rounding-in-javascript/ this is suggested instead:
function round(value, decimals) {
return Number(Math.round(value+'e'+decimals)+'e-'+decimals);
}
PHP-Like rounding Method
The code below can be used to add your own version of Math.round to your own namespace which takes a precision parameter. Unlike Decimal rounding in the example above, this performs no conversion to and from strings, and the precision parameter works same way as PHP and Excel whereby a positive 1 would round to 1 decimal place and -1 would round to the tens.
var myNamespace = {};
myNamespace.round = function(number, precision) {
var factor = Math.pow(10, precision);
var tempNumber = number * factor;
var roundedTempNumber = Math.round(tempNumber);
return roundedTempNumber / factor;
};
myNamespace.round(1234.5678, 1); // 1234.6
myNamespace.round(1234.5678, -1); // 1230
from Mozilla Developer reference for Math.round()
Hopefully working code (didn't do much testing):
function toFixed(value, precision) {
var precision = precision || 0,
neg = value < 0,
power = Math.pow(10, precision),
value = Math.round(value * power),
integral = String((neg ? Math.ceil : Math.floor)(value / power)),
fraction = String((neg ? -value : value) % power),
padding = new Array(Math.max(precision - fraction.length, 0) + 1).join('0');
return precision ? integral + '.' + padding + fraction : integral;
}
I think below function can help
function roundOff(value,round) {
return (parseInt(value * (10 ** (round + 1))) - parseInt(value * (10 ** round)) * 10) > 4 ? (((parseFloat(parseInt((value + parseFloat(1 / (10 ** round))) * (10 ** round))))) / (10 ** round)) : (parseFloat(parseInt(value * (10 ** round))) / ( 10 ** round));
}
usage : roundOff(600.23458,2); will return 600.23
function roundton(num, n) {
return Number(num.toFixed(n));
}
This uses JS's built-in method Number.prototype.toFixed which is meant for formatting strings but allows us to round to a specific number of digits. the Number() call converts it back to a number object cleanly
Ideally, we wouldn't need to convert it to a string, but toFixed is written in native C++ doing basic cstring operations so it's likely still fast.
If you do not really care about rounding, just added a toFixed(x) and then removing trailing 0es and the dot if necessary. It is not a fast solution.
function format(value, decimals) {
if (value) {
value = value.toFixed(decimals);
} else {
value = "0";
}
if (value.indexOf(".") < 0) { value += "."; }
var dotIdx = value.indexOf(".");
while (value.length - dotIdx <= decimals) { value += "0"; } // add 0's
return value;
}