These two functions fulfill all the tests carried out.
1. Adapted from MDN ( recommended solution )
Taking the code from Rounding Decimal , I adapted the solution to perform a symmetric arithmetic rounding with negative numbers. That is, round -1.5 ≈ -2.
function round(num, decimales = 2) {
var signo = (num >= 0 ? 1 : -1);
num = num * signo;
if (decimales === 0) //con 0 decimales
return signo * Math.round(num);
// round(x * 10 ^ decimales)
num = num.toString().split('e');
num = Math.round(+(num[0] + 'e' + (num[1] ? (+num[1] + decimales) : decimales)));
// x * 10 ^ (-decimales)
num = num.toString().split('e');
return signo * (num[0] + 'e' + (num[1] ? (+num[1] - decimales) : -decimales));
}
The Intl.NumberFormat([locales[, options]])
function allows you to round a number correctly with native code. However, as will be seen later, this function accepts language-sensitive options, making it significantly slower.
function intlRound(numero, decimales = 2, usarComa = false) {
var opciones = {
maximumFractionDigits: decimales,
useGrouping: false
};
usarComa = usarComa ? "es" : "en";
return new Intl.NumberFormat(usarComa, opciones).format(numero);
}
Returns a string with the representation of the number, rounded to the maximum of decimal places. If you want to convert the number, apply parseFloat()
to the result.
Discussion
When manipulating numbers by performing floating point operations , there is a limitation to represent them accurately. JavaScript uses a 64-bit floating-point representation, with associated limitations. For more information, read Why can not my programs do arithmetic calculations correctly? .
This implies that there are numbers for which your representation has problems to round correctly . However, the methods used in this response solve those problems.
In addition, there is another peculiar behavior that is being avoided: Math.round ( ) rounds a 5 in the first non-significant position of a negative to 0 .
Math.round( 1.5); // == 2
Math.round(-1.5); // == -1 -problema!
Math.round(-1.6); // == -2
Math.round( 0.5); // == 1
Math.round(-0.5); // == -0 -sí, "-0"
In the next test, the rounding of negative numbers for Math.round()
was corrected and the cases in which each one can fail are compared.
Results of each response:
/* -----------------------------------------------------------
* Funciones de cada respuesta
* ----------------------------------------------------------- */
function roundNumber (number, max = 2) {
//Respuesta de Guz
let fractionalPart = number.toString().split('.')[1];
if (!fractionalPart || fractionalPart.length <= 2) {
return number;
}
return Number(number.toFixed(max));
}
function mathRound (num) {
//Respuesta de Rubén
return Math.round(num * 100) / 100 ;
}
function mathRound2 (num, decimales = 2) {
//Respuesta de Rubén modificada por mí para el caso general y números negativos
var exponente = Math.pow(10, decimales);
return (num >= 0 || -1) * Math.round(Math.abs(num) * exponente) / exponente;
}
function redondearDecimales (numero, decimales = 2) {
//Respuesta de Enrique B.
numeroRegexp = new RegExp('\d\.(\d){' + decimales + ',}');
if (numeroRegexp.test(numero)) {
return Number(numero.toFixed(decimales));
} else {
return Number(numero.toFixed(decimales)) === 0 ? 0 : numero;
}
}
function redondear(x, decimales = 2)
{ //Respuesta de ArtEze
var texto=x+""
var poco=texto.search("e-")
if(poco>=0)
{
var decimales_salida=texto.slice(poco+2)*1
return decimales<decimales_salida?0:x
}
var mucho=texto.search("e+")
if(mucho>=0)
{
return x
}
var punto=texto.search("\.")
var cortado=texto.slice(0,punto+decimales+2)
var longitud=cortado.length
var decimales_ingresado=longitud-punto-1
for(var i=decimales_ingresado;i<=decimales;i++)
{
cortado+="0"
}
longitud=cortado.length
var último=cortado.slice(longitud-1)
var anteúltimo=cortado.slice(longitud-2,longitud-1)
if(último*1>=5)
{
anteúltimo=(anteúltimo*1)+1
}
cortado=cortado.slice(0,longitud-2)+""+anteúltimo
return cortado*1
}
function intlRound (numero, decimales = 2, usarComa = false) {
//Esta respuesta
var opciones = {
maximumFractionDigits: decimales,
useGrouping: false
};
return new Intl.NumberFormat((usarComa ? "es" : "en"), opciones).format(numero);
}
function round(num, decimales = 2) {
var signo = (num >= 0 ? 1 : -1);
num = num * signo;
if (decimales === 0) //con 0 decimales
return signo * Math.round(num);
// round(x * 10 ^ decimales)
num = num.toString().split('e');
num = Math.round(+(num[0] + 'e' + (num[1] ? (+num[1] + decimales) : decimales)));
// x * 10 ^ (-decimales)
num = num.toString().split('e');
return signo * (num[0] + 'e' + (num[1] ? (+num[1] - decimales) : -decimales));
}
/* -----------------------------------------------------------
* PRUEBAS
* ----------------------------------------------------------- */
let funciones = [roundNumber, mathRound, mathRound2, redondearDecimales, redondear, intlRound, round],
pruebas = [
["Básica", 1.445, 1.45],
["Negativo", -1.445, -1.45],
["Nueves", 1.997, 2],
["Número grande", 1.1e+21, 1.1e+21],
["Número chico", 0.0000007, 0],
["Número chico != 0", [9e-8,7], 1e-7],
["Problema con coma flotante", 1.005, 1.01]
],
resultado = [];
pruebas.forEach(function(prueba){
let numero = prueba[1],
esperado = prueba[2],
resPrueba = [prueba[0]+" ("+numero+" ⟶ "+esperado+")"];
funciones.forEach(function(funcion){
let devuelto = (typeof numero == 'number' ? funcion(numero) : funcion(...numero));
if (devuelto == esperado) {
resPrueba.push("✔ "+funcion.name);
} else {
resPrueba.push("❌ "+funcion.name+" ("+devuelto+")");
}
});
resultado.push(resPrueba.join("\n\t"));
});
document.getElementById("resultado")
.innerText = resultado.join("\n");
<pre id="resultado"></pre>
However, effectiveness has its counterpart in performance (see benchmark ).
+--------------------+-----------+
| Función | Ops/seg |
+--------------------+-----------+
| mathRound | 2,387,155 |
| mathRound2 | 1,531,698 |
| roundNumber | 114,499 |
| redondear2 | 180,980 |
| redondearDecimales | 83,631 |
| round | 50,800 |
| intlRound | 2,243 |
+--------------------+-----------+
*Chrome 55
In conclusion:
- The solution with
Math.round()
is the fastest, but it fails with negative values and has precision problems with floating point.
- By modifying it as
mathRound2()
(in this same answer), it was extended to the general case and the error was resolved with negative values. It's still faster than the rest, but it still does not solve the precision error .
- The solution with
Intl.NumberFormat()
of this response returns the correct result in all cases, but is significantly slower than the others.
- The solution of% co_of% of rounding is the one that, fulfilling all the tests, performs better . It is significantly slower than
round()
, but still achieves 50k operations per second, something more than acceptable for many scenarios, so it is recommended for the general case in which you want an accurate answer and no rounding errors.