¿Cómo redondear a 2 lugares decimales como máximo, si es necesario?

3317

Me gustaría redondear como máximo 2 lugares decimales, pero solo si es necesario .

Aporte:

10
1.7777777
9.1

Producción:

10
1.78
9.1

¿Cómo puedo hacer esto en JavaScript?

0
4336

Utilizar Math.round():

Math.round(num * 100) / 100

O para ser más específico y para asegurar que cosas como 1.005 redondeen correctamente, use Number.EPSILON :

Math.round((num + Number.EPSILON) * 100) / 100
12
  • Math.round ((224.98499999 + Number.EPSILON) * 100) / 100 224.98 donde debería haber ben 224.95, ¿verdad? Satish Patro 5 de febrero a las 8:03
  • 3
    @PSatishPatro (supongo que quiso decir 224,99 y no 224,95). si está redondeando al segundo decimal (centésimas), solo debería importarnos qué número es el tercer decimal (milésimo) y todo lo que sigue a ese decimal se elimina. Entonces, de la entrada 224.98499999, solo importa 224.984, lo que significa que 224.98 es correcto. Francisc0 11 feb a las 18:37
  • 1
    Pude averiguar qué estaba pasando. Resulta que Javascript vio el valor como una cadena y no como un número. Esto realmente arruina las cosas. Una vez que Icon convirtió la cadena en un número usando Number.parseFloat, funcionó perfectamente. Martijn Hiemstra 9 de marzo a las 9:22
  • 3
    Math.round (1.255 * 100) / 100 será 1.25. que está malSun 23 de marzo a las 4:49
  • 3
    @PSatishPatro estamos fuera de tema, me doy cuenta, pero redondear 224.9849 ... a dos lugares decimales debería, en cualquier idioma o a mano, resultar en 224.98. Si obtiene 224,99, me temo que lo hizo mal. La forma más sencilla de pensar en ello es que estás buscando el número más cercano con solo dos lugares decimales. Si bien no hay mucha diferencia, 224,9849 está más cerca de 224,98 que de 224,99. Blackbeard 25 de marzo a las 20:28
3550

Si el valor es un tipo de texto:

parseFloat("123.456").toFixed(2);

Si el valor es un número:

var numb = 123.23454;
numb = numb.toFixed(2);

Hay una desventaja de que valores como 1,5 darán "1,50" como salida. Una solución sugerida por @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.

Parece que Math.roundes una mejor solución. ¡Pero no lo es! En algunos casos NO se redondeará correctamente:

Math.round(1.005 * 1000)/1000 // Returns 1 instead of expected 1.01!

toFixed () será también NO ronda correctamente en algunos casos (probado en Chrome v.55.0.2883.87)!

Ejemplos:

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.

Supongo que esto se debe a que 1.555 es en realidad algo así como flotar 1.55499994 detrás de escena.

La solución 1 es utilizar un script con el algoritmo de redondeo requerido, por ejemplo:

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);
  }
}

https://plnkr.co/edit/uau8BlS1cqbvWPCHJeOy?p=preview

NOTA: Esta no es una solución universal para todos. Hay varios algoritmos de redondeo diferentes, su implementación puede ser diferente, depende de sus requisitos. https://en.wikipedia.org/wiki/Rounding

La solución 2 es evitar los cálculos de front-end y extraer valores redondeados del servidor back-end.

Editar: Otra posible solución, que tampoco es a prueba de balas.

Math.round((num + Number.EPSILON) * 100) / 100

En algunos casos, cuando redondea un número como 1.3549999999999998, devolverá un resultado incorrecto. Debería ser 1,35 pero el resultado es 1,36.

3
  • 2
    Esta es la respuesta más correcta para todos los puntos. Captain Prinny 5 de febrero a las 16:42
  • 2
    Especialmente me encanta el pequeño truco de @minitech para evitar decimales innecesarios (numb = numb.toFixed (2);)Hans Dash 13 de marzo a las 16:05
  • en esta función roundNumberV2existe esta condición if (Math.pow(0.1, scale) > num) { return 0; }. ¿Puedo saber cuál es el propósito de esta condición? Mei Lie 18 de agosto a las 11:57
540

Puedes usar

function roundToTwo(num) {    
    return +(Math.round(num + "e+2")  + "e-2");
}

Encontré esto en MDN . Su camino evita el problema con 1.005 que se mencionó .

roundToTwo(1.005)
1.01
roundToTwo(10)
10
roundToTwo(1.7777777)
1.78
roundToTwo(9.1)
9.1
roundToTwo(1234.5678)
1234.57
2
  • 13
    @Redsandro, +(val)es la coerción equivalente a usar Number(val). La concatenación de "e-2" a un número dio como resultado una cadena que necesitaba volver a convertirse en un número. Jack 28 feb 2014 a las 19:11
  • 3
    Pase un número con e y devuelve NaN, por ejemplo, 1.19e-7Antony Ng 8 de enero a las 10:02
172

La respuesta de MarkG es la correcta. Aquí hay una extensión genérica para cualquier número de decimales.

Number.prototype.round = function(places) {
  return +(Math.round(this + "e+" + places)  + "e-" + places);
}

Uso:

var n = 1.7777;    
n.round(2); // 1.78

Prueba de unidad:

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');
  });
})
3
  • Encuentro esta prototypeversión independiente (sin extensión) (ES6) fácil de leer y sencilla: round = (num, precision) => Number(Math.round(num + "e+" + precision) + "e-" + precision);Daniel Dut 1 de febrero a las 0:04
  • 2
    ¿Qué pasa si el número de entrada ya está en forma exponencial? Obtendrás NaNLearner 5 de marzo a las 7:50
  • Recibo este error en este (Math.round (número + "e +" + lugares)) El argumento del tipo 'cadena' no se puede asignar al parámetro del tipo 'número' en Typecriptjuanjinario 16 de junio a las 9:36
160

Deberías usar:

Math.round( num * 100 + Number.EPSILON ) / 100

Nadie parece darse cuenta Number.EPSILON.

También vale la pena señalar que esto no es una rareza de JavaScript como algunas personas afirmaron.

Esa es simplemente la forma en que funcionan los números de punto flotante en una computadora. Como el 99% de los lenguajes de programación, JavaScript no tiene números de punto flotante hechos en casa ; depende de la CPU / FPU para eso. Una computadora usa binario, y en binario, no hay números como 0.1, sino una mera aproximación binaria para eso. ¿Por qué? Por la misma razón que 1/3 no se puede escribir en decimal: su valor es 0.33333333 ... con un infinito de tres.

Aquí ven Number.EPSILON. Ese número es la diferencia entre 1 y el siguiente número existente en los números de coma flotante de doble precisión. Eso es todo: no hay un número entre 1y 1 + Number.EPSILON.

EDITAR:

Como se pregunta en los comentarios, aclaremos una cosa: sumar Number.EPSILONes relevante solo cuando el valor a redondear es el resultado de una operación aritmética, ya que puede tragarse algún error delta de coma flotante.

No es útil cuando el valor proviene de una fuente directa (por ejemplo: literal, entrada del usuario o sensor).

EDITAR (2019):

Como @maganap y algunas personas han señalado, es mejor agregar Number.EPSILONantes de multiplicar:

Math.round( ( num + Number.EPSILON ) * 100 ) / 100

EDITAR (diciembre de 2019):

Últimamente, uso una función similar a esta para comparar números con conocimiento de épsilon:

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 ;
}

Mi caso de uso es una biblioteca de validación de datos de aserción + que estoy desarrollando durante muchos años.

De hecho, en el código que estoy usando ESPILON_RATE = 1 + 4 * Number.EPSILONy EPSILON_ZERO = 4 * Number.MIN_VALUE(cuatro veces el épsilon), porque quiero un verificador de igualdad lo suficientemente suelto para acumular el error de punto flotante.

Hasta ahora, me parece perfecto. Espero que te ayude.

6
  • ¿Debo usar 1000 en lugar de 100 si quiero redondear a 3 números decimales? AliN11 31 de ene a las 17:55
  • Math.round ((224.98499999 * 100 + Number.EPSILON)) / 100 224.98 En lugar de 224.99Satish Patro 5 de febrero a las 8:05
  • @PSatishPatro Eso es correcto. .849 está más cerca de .8 que de .9, por lo tanto, se redondea a .8. Random Elephant 11 de mayo a las 10:19
  • @RandomElephant, está bien, pero en general, cuando calculamos, hacemos un redondeo hacia arriba, lo que equivale a redondear a la MITAD del último dígito. 98499 -> .9849 -> .985 -> .99. ¿Hay alguna forma de lograr esto en js? Satish Patro 13 de mayo a las 8:17
  • @PSatishPatro Lo hay, pero es una matemática incorrecta. No hay un redondeo general donde comienzas desde el último dígito y, si lo haces, debes considerar seriamente volver a aprender matemáticas. Editar: para responder, tomaría la longitud de los dígitos del número y los enlazaría desde el último, redondeando cada uno y cambiando el número inicial hasta llegar al recuento de lugares deseado. Random Elephant 13 de mayo a las 8:54
96
+50

Esta pregunta es complicada.

Supongamos que tenemos una función, roundTo2DP(num)que toma un flotante como argumento y devuelve un valor redondeado a 2 lugares decimales. ¿A qué debería evaluar cada una de estas expresiones?

  • roundTo2DP(0.014999999999999999)
  • roundTo2DP(0.0150000000000000001)
  • roundTo2DP(0.015)

La respuesta 'obvia' es que el primer ejemplo debería redondearse a 0,01 (porque está más cerca de 0,01 que de 0,02) mientras que los otros dos deberían redondearse a 0,02 (porque 0,0150000000000000001 está más cerca de 0,02 que de 0,01, y porque 0,015 está exactamente a medio camino entre ellos y existe una convención matemática de que dichos números se redondean).

El problema, que puede haber adivinado, es que roundTo2DP no se puede implementar para dar esas respuestas obvias, porque los tres números que se le pasan son el mismo número . Los números de punto flotante binario IEEE 754 (el tipo utilizado por JavaScript) no pueden representar exactamente la mayoría de los números que no son enteros, por lo que los tres literales numéricos anteriores se redondean a un número de punto flotante válido cercano. Este número, da la casualidad, es exactamente

0.01499999999999999944488848768742172978818416595458984375

que está más cerca de 0.01 que de 0.02.

Puede ver que los tres números son iguales en la consola de su navegador, shell de nodo u otro intérprete de JavaScript. Simplemente compárelos:

> 0.014999999999999999 === 0.0150000000000000001
true

Entonces, cuando escribo m = 0.0150000000000000001, el valor exacto con elm que termino está más cerca de 0.01lo que está 0.02. Y, sin embargo, si me convierto men String ...

> var m = 0.0150000000000000001;
> console.log(String(m));
0.015
> var m = 0.014999999999999999;
> console.log(String(m));
0.015

... obtengo 0.015, que debería redondearse a 0.02, y que notablemente no es el número de 56 posiciones decimales que dije anteriormente que todos estos números eran exactamente iguales. Entonces, ¿qué magia oscura es esta?

La respuesta se puede encontrar en la especificación ECMAScript, en la sección 7.1.12.1: ToString aplicado al tipo Number . Aquí se establecen las reglas para convertir un número m en una cadena. La parte clave es el punto 5, en el que se genera un entero s cuyos dígitos se utilizarán en la representación String de m :

let n, k, and s be integers such that k ≥ 1, 10k-1s < 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.

La parte clave aquí es el requisito de que " k sea ​​lo más pequeño posible". Ese requisito equivale a un requisito de que, dado un Número m, el valor de String(m)debe tener el menor número posible de dígitos y, al mismo tiempo, satisfacer el requisito de que Number(String(m)) === m. Como ya lo sabemos 0.015 === 0.0150000000000000001, ahora está claro por qué String(0.0150000000000000001) === '0.015'debe ser cierto.

Por supuesto, nada de esta discusión ha respondido directamente a lo que roundTo2DP(m) debería devolver. Si mel valor exacto es 0.01499999999999999944488848768742172978818416595458984375, pero su representación de cadena es '0.015', entonces ¿cuál es la respuesta correcta , matemática, práctica, filosófica o lo que sea, cuando la redondeamos a dos lugares decimales?

No hay una única respuesta correcta a esto. Depende de su caso de uso. Probablemente desee respetar la representación de String y redondear hacia arriba cuando:

  • El valor que se representa es intrínsecamente discreto, por ejemplo, una cantidad de moneda en una moneda de 3 decimales como los dinares. En este caso, el valor real de un número como 0.015 es 0.015, y la representación 0.0149999999 ... que obtiene en coma flotante binaria es un error de redondeo. (Por supuesto, muchos argumentarán, razonablemente, que debe usar una biblioteca decimal para manejar dichos valores y nunca representarlos como números de coma flotante binarios en primer lugar).
  • El valor fue escrito por un usuario. En este caso, nuevamente, el número decimal exacto ingresado es más "verdadero" que la representación de coma flotante binaria más cercana.

Por otro lado, probablemente desee respetar el valor de punto flotante binario y redondear hacia abajo cuando su valor sea de una escala inherentemente continua, por ejemplo, si es una lectura de un sensor.

Estos dos enfoques requieren un código diferente. Para respetar la representación de cadena del número, podemos (con bastante código razonablemente sutil) implementar nuestro propio redondeo que actúa directamente sobre la representación de cadena, dígito a dígito, utilizando el mismo algoritmo que habría utilizado en la escuela cuando se les enseñó a redondear números. A continuación se muestra un ejemplo que respeta el requisito del OP de representar el número con 2 decimales "solo cuando sea necesario" eliminando los ceros finales después del punto decimal; es posible que, por supuesto, necesite ajustarlo a sus necesidades precisas.

/**
 * 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;
    }

    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.
        finalNumber = Number(beforePoint)+1;
    } 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+$/, '')
}

Uso de ejemplo:

> 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'

La función anterior es probablemente la que desee utilizar para evitar que los usuarios vean que los números que han ingresado se redondean incorrectamente.

(Como alternativa, también puede probar la biblioteca round10 que proporciona una función de comportamiento similar con una implementación tremendamente diferente).

Pero, ¿qué pasa si tiene el segundo tipo de Número, un valor tomado de una escala continua, donde no hay razón para pensar que las representaciones decimales aproximadas con menos lugares decimales son más precisas que las que tienen más? En ese caso, no queremos respetar la representación String, porque esa representación (como se explica en la especificación) ya está algo redondeada; no queremos cometer el error de decir "0.014999999 ... 375 redondea a 0.015, que se redondea a 0.02, entonces 0.014999999 ... 375 redondea a 0.02".

Aquí simplemente podemos usar el toFixedmétodo incorporado . Tenga en cuenta que al llamar Number()a la Cadena devuelta por toFixed, obtenemos un Número cuya representación de Cadena no tiene ceros finales (gracias a la forma en que JavaScript calcula la representación de Cadena de un Número, discutida anteriormente en esta respuesta).

/**
 * 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);
}
1
  • Su método falla en 16,996 redondeando a 2 decimales. Entra en esta rama: else if (/^9+$/.test(afterPoint)) {, y luego finalNumber es un número y no una cadena que falla al final: return finalNumber.replace (/ 0 + $ /, ' ')Sergii 28 de julio a las 7:01
90

Considere .toFixed()y .toPrecision():

http://www.javascriptkit.com/javatutors/formatnumber.shtml

2
  • En Firefox, 3.9935.toFixed(3) → "3.994", 3.9945.toFixed(3) → "3.994", 3.9955.toFixed(3) → "3.995", 3.9965.toFixed(3) → "3.997". ¿Es el comportamiento esperado? Por ejemplo, ¿no debería 3.9945.toFixed(3)regresar "3.995"o 3.9955.toFixed(3)regresar "3.996"? Yeheshuah 22 de junio a las 6:50
  • Un Kunin ha contado un poco sobre esto en la respuesta a continuación. Yeheshuah 22 de junio a las 7:03
87

Se puede usar .toFixed(NumberOfDecimalPlaces).

var str = 10.234.toFixed(2); // => '10.23'
var number = Number(str); // => 10.23
0
74

En general, el redondeo decimal se realiza escalando: round(num * p) / p

Implementación ingenua

Al usar la siguiente función con números a la mitad, obtendrá el valor redondeado superior como se esperaba, o el valor redondeado inferior a veces dependiendo de la entrada.

Esto inconsistencyen el redondeo puede introducir errores difíciles de detectar en el código del cliente.

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)

Para determinar si una operación de redondeo implica un valor de punto medio, la función Round multiplica el valor original a redondear por 10 ** n, donde n es el número deseado de dígitos fraccionarios en el valor de retorno, y luego determina si el valor fraccionario restante parte del valor es mayor o igual a 0,5. Esto "Exact Testing for Equality"con valores de punto flotante es problemático debido a los problemas del formato de punto flotante con la representación binaria y la precisión. Esto significa que cualquier porción fraccionaria de un número que sea levemente menor que .5 (debido a una pérdida de precisión) no se redondeará al alza.

En el ejemplo anterior, 5.015es un valor de punto medio si se va a redondear a dos lugares decimales, el valor de 5.015 * 100 es en realidad 501.49999999999994. Debido a que .49999999999994 es menor que .5, se redondea a 501 y finalmente el resultado es 5.01.

Mejores implementaciones

Notación exponencial

Al convertir el número en una cadena en notación exponencial, los números positivos se redondean como se esperaba. Pero tenga en cuenta que los números negativos se redondean de manera diferente a los números positivos.

De hecho, realiza lo que es básicamente equivalente a "redondear a la mitad" como la regla, verá que se round(-1.005, 2)evalúa a -1aunque round(1.005, 2)evalúa a 1.01. El método lodash _.round utiliza esta técnica.

/**
 * 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 = 0) {
    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

Si desea el comportamiento habitual al redondear números negativos, deberá convertir los números negativos en positivos antes de llamar a Math.round () , y luego convertirlos nuevamente en números negativos antes de regresar.

// 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);
}

Redondeo aproximado

Para corregir el problema de redondeo mostrado en el naiveRoundejemplo anterior , podemos definir una función de redondeo personalizada que realice una prueba "casi igual" para determinar si un valor fraccionario está lo suficientemente cerca de un valor de punto medio para estar sujeto al redondeo de punto medio.

// 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, 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

Número.EPSILON

Existe una técnica diferente, puramente matemática, para realizar el redondeo al más cercano (utilizando "redondear a la mitad desde cero" ), en la que se aplica la corrección épsilon antes de llamar a la función de redondeo.

Simplemente, agregamos el valor flotante más pequeño posible (= 1.0 ulp; unidad en el último lugar) al producto antes de redondear. Esto se mueve al siguiente valor flotante representable, alejándose de cero, por lo que compensará el error de redondeo binario que puede ocurrir durante la multiplicación por 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;
}

// 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

Después de añadir 1 ULP, el valor de 5.015 * 100 que está 501.49999999999994será corregido para 501.50000000000006, esto redondea a 502 y, finalmente, el resultado es 5,02.

Tenga en cuenta que el tamaño de una unidad en el último lugar ("ulp") está determinado por (1) la magnitud del número y (2) el épsilon relativo de la máquina (2 ^ -52). Los ulps son relativamente más grandes en números con magnitudes más grandes que en números con magnitudes más pequeñas.

Redondeo doble

Aquí, usamos el método toPrecision () para eliminar los errores de redondeo de punto flotante en los cálculos intermedios. Simplemente, redondeamos a 15 cifras significativas para eliminar el error de redondeo en el decimosexto dígito significativo. Esta técnica para redondear el resultado a dígitos significativos también es utilizada por la función de redondeo de PHP 7 .

El valor de 5.015 * 100 que se 501.49999999999994redondeará primero a 15 dígitos significativos 501.500000000000, luego se redondeará nuevamente a 502 y finalmente el resultado es 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;
}

// 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

Biblioteca JavaScript de precisión arbitraria - decimal.js

// Round half away from zero
function round(num, decimalPlaces = 0) {
    return new Decimal(num).toDecimalPlaces(decimalPlaces).toNumber();
}

// 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
<script src="https://cdnjs.cloudflare.com/ajax/libs/decimal.js/10.2.1/decimal.js" integrity="sha512-GKse2KVGCCMVBn4riigHjXE8j5hCxYLPXDw8AvcjUtrt+a9TbZFtIKGdArXwYOlZvdmkhQLWQ46ZE3Q1RIa7uQ==" crossorigin="anonymous"></script>

Solución 1: cadena en notación exponencial

Inspirado en la solución proporcionada por KFish aquí: https://stackoverflow.com/a/55521592/4208440

Una solución sencilla que proporciona redondeo de decimales, piso y techo precisos a un número específico de lugares decimales sin agregar una biblioteca completa. Trata los flotantes más como decimales al corregir los problemas de redondeo binario para evitar resultados inesperados: por ejemplo, el piso ((0.1 + 0.7) * 10) devolverá el resultado esperado 8.

Los números se redondean a un número específico de dígitos fraccionarios. Si especifica una precisión negativa, se redondeará a cualquier número de lugares a la izquierda del punto decimal.

// Solution 1
var DecimalPrecision = (function() {
    if (Math.sign === undefined) {
        Math.sign = function(x) {
            return ((x > 0) - (x < 0)) || +x;
        };
    }
    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);         // 0.01
console.log(DecimalPrecision.floor(1e-8, 2) === 0);              // 0

// testing simple cases
console.log(DecimalPrecision.round(5.12, 1) === 5.1);          // 5.1
console.log(DecimalPrecision.round(-5.12, 1) === -5.1);       // -5.1
console.log(DecimalPrecision.ceil(5.12, 1) === 5.2);           // 5.2
console.log(DecimalPrecision.ceil(-5.12, 1) === -5.1);        // -5.1
console.log(DecimalPrecision.floor(5.12, 1) === 5.1);          // 5.1
console.log(DecimalPrecision.floor(-5.12, 1) === -5.2);       // -5.2
console.log(DecimalPrecision.trunc(5.12, 1) === 5.1);          // 5.1
console.log(DecimalPrecision.trunc(-5.12, 1) === -5.1);       // -5.1

// testing edge cases for round
console.log(DecimalPrecision.round(1.005, 2) === 1.01);       // 1.01
console.log(DecimalPrecision.round(39.425, 2) === 39.43);    // 39.43
console.log(DecimalPrecision.round(-1.005, 2) === -1.01);    // -1.01
console.log(DecimalPrecision.round(-39.425, 2) === -39.43); // -39.43

// testing edge cases for ceil
console.log(DecimalPrecision.ceil(9.130, 2) === 9.13);        // 9.13
console.log(DecimalPrecision.ceil(65.180, 2) === 65.18);     // 65.18
console.log(DecimalPrecision.ceil(-2.260, 2) === -2.26);     // -2.26
console.log(DecimalPrecision.ceil(-18.150, 2) === -18.15);  // -18.15

// testing edge cases for floor
console.log(DecimalPrecision.floor(2.260, 2) === 2.26);       // 2.26
console.log(DecimalPrecision.floor(18.150, 2) === 18.15);    // 18.15
console.log(DecimalPrecision.floor(-9.130, 2) === -9.13);    // -9.13
console.log(DecimalPrecision.floor(-65.180, 2) === -65.18); // -65.18

// testing edge cases for trunc
console.log(DecimalPrecision.trunc(2.260, 2) === 2.26);       // 2.26
console.log(DecimalPrecision.trunc(18.150, 2) === 18.15);    // 18.15
console.log(DecimalPrecision.trunc(-2.260, 2) === -2.26);    // -2.26
console.log(DecimalPrecision.trunc(-18.150, 2) === -18.15); // -18.15

// testing round to tens and hundreds
console.log(DecimalPrecision.round(1262.48, -1) === 1260);    // 1260
console.log(DecimalPrecision.round(1262.48, -2) === 1300);    // 1300

// testing toFixed()
console.log(DecimalPrecision.toFixed(1.005, 2) === "1.01");   // "1.01"

Solución 2: puramente matemática (Number.EPSILON)

Esta solución evita cualquier conversión / manipulación de cadenas de cualquier tipo por motivos de rendimiento.

Solution 1: 25,838 ops/sec

Solution 2: 655,087 ops/sec

http://jsbench.github.io/#31ec3a8b3d22bd840f8e6822e681a3ac

// Solution 2
var DecimalPrecision2 = (function() {
    if (Number.EPSILON === undefined) {
        Number.EPSILON = Math.pow(2, -52);
    }
    if (Math.trunc === undefined) {
        Math.trunc = function(v) {
            return v < 0 ? Math.ceil(v) : Math.floor(v);
        };
    }
    var isRound = function(num, decimalPlaces) {
        //return decimalPlaces >= 0 &&
        //    +num.toFixed(decimalPlaces) === num;
        var p = Math.pow(10, decimalPlaces);
        return Math.round(num * p) / p === num;
    };
    var decimalAdjust = function(type, num, decimalPlaces) {
        if (isRound(num, decimalPlaces || 0))
            return num;
        var p = Math.pow(10, decimalPlaces || 0);
        var n = (num * p) * (1 + Number.EPSILON);
        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(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.130, 2) === 9.13);
console.log(DecimalPrecision2.ceil(65.180, 2) === 65.18);
console.log(DecimalPrecision2.ceil(-2.260, 2) === -2.26);
console.log(DecimalPrecision2.ceil(-18.150, 2) === -18.15);

// testing edge cases for floor
console.log(DecimalPrecision2.floor(2.260, 2) === 2.26);
console.log(DecimalPrecision2.floor(18.150, 2) === 18.15);
console.log(DecimalPrecision2.floor(-9.130, 2) === -9.13);
console.log(DecimalPrecision2.floor(-65.180, 2) === -65.18);

// testing edge cases for trunc
console.log(DecimalPrecision2.trunc(2.260, 2) === 2.26);
console.log(DecimalPrecision2.trunc(18.150, 2) === 18.15);
console.log(DecimalPrecision2.trunc(-2.260, 2) === -2.26);
console.log(DecimalPrecision2.trunc(-18.150, 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");

Solución 3: doble redondeo

Esta solución utiliza el método toPrecision () para eliminar los errores de redondeo de punto flotante.

// Solution 3
var DecimalPrecision3 = (function() {
    if (Math.sign === undefined) {
        Math.sign = function(x) {
            return ((x > 0) - (x < 0)) || +x;
        };
    }
    if (Math.trunc === undefined) {
        Math.trunc = function(v) {
            return v < 0 ? Math.ceil(v) : Math.floor(v);
        };
    }
    // 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 = Math.pow(10, 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.130, 2) === 9.13);
console.log(DecimalPrecision3.ceil(65.180, 2) === 65.18);
console.log(DecimalPrecision3.ceil(-2.260, 2) === -2.26);
console.log(DecimalPrecision3.ceil(-18.150, 2) === -18.15);

// testing edge cases for floor
console.log(DecimalPrecision3.floor(2.260, 2) === 2.26);
console.log(DecimalPrecision3.floor(18.150, 2) === 18.15);
console.log(DecimalPrecision3.floor(-9.130, 2) === -9.13);
console.log(DecimalPrecision3.floor(-65.180, 2) === -65.18);

// testing edge cases for trunc
console.log(DecimalPrecision3.trunc(2.260, 2) === 2.26);
console.log(DecimalPrecision3.trunc(18.150, 2) === 18.15);
console.log(DecimalPrecision3.trunc(-2.260, 2) === -2.26);
console.log(DecimalPrecision3.trunc(-18.150, 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");

Solución 4: doble redondeo v2

Esta solución es como la Solución 3, sin embargo, utiliza una toPrecision()función personalizada .

// Solution 4
var DecimalPrecision4 = (function() {
    if (Math.sign === undefined) {
        Math.sign = function(x) {
            return ((x > 0) - (x < 0)) || +x;
        };
    }
    if (Math.trunc === undefined) {
        Math.trunc = function(v) {
            return v < 0 ? Math.ceil(v) : Math.floor(v);
        };
    }
    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; !Number.isInteger(num * p); p *= 10) decimalPlaces++;
        if (shift >= decimalPlaces)
            return num;
        // Round to "shift" fractional digits
        var scale = Math.pow(10, 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 = Math.pow(10, 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.130, 2) === 9.13);
console.log(DecimalPrecision4.ceil(65.180, 2) === 65.18);
console.log(DecimalPrecision4.ceil(-2.260, 2) === -2.26);
console.log(DecimalPrecision4.ceil(-18.150, 2) === -18.15);

// testing edge cases for floor
console.log(DecimalPrecision4.floor(2.260, 2) === 2.26);
console.log(DecimalPrecision4.floor(18.150, 2) === 18.15);
console.log(DecimalPrecision4.floor(-9.130, 2) === -9.13);
console.log(DecimalPrecision4.floor(-65.180, 2) === -65.18);

// testing edge cases for trunc
console.log(DecimalPrecision4.trunc(2.260, 2) === 2.26);
console.log(DecimalPrecision4.trunc(18.150, 2) === 18.15);
console.log(DecimalPrecision4.trunc(-2.260, 2) === -2.26);
console.log(DecimalPrecision4.trunc(-18.150, 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

Aquí hay un punto de referencia que compara las operaciones por segundo en las soluciones anteriores en Chrome 85.0.4183.83. Obviamente, todos los navegadores son diferentes, por lo que su kilometraje puede variar.

Comparación de referencia (Nota: cuanto más, mejor)

Gracias @ Mike por agregar una captura de pantalla del punto de referencia.

10
  • Bien, veo que hiciste una prueba de rendimiento más exhaustiva sobre la diferencia. Acabo de hacer una comparación rápida en devtools y regresaron con una variación muy similar en el tiempo de ejecución, pero me preguntaba si la diferencia de rendimiento comenzaría a mostrarse a un volumen / frecuencia realmente alto. KFish 24/08/20 a las 13:58
  • 2
    Ok, lo acabo de agregar para ti, así como una captura de pantalla del punto de referencia. Mike 3 sep.20 a las 22:40
  • 1
    Gracias @ Mike por actualizar la respuestaAmr Ali 3 sep.2020 a las 22:54
  • 3
    Hola @AmrAli. Esta es una respuesta asombrosa. Uno de los pocos que son lo más precisos posible. ¡Gracias! 👍 Me gusta especialmente Solution 2por su velocidad. Una cosa que noté es que la velocidad se puede aumentar en ~ 5-10% si isRoundse elimina el cheque de devolución anticipada . Agrega más operaciones que solo ejecutar la decimalAdjustfunción. Regresar temprano usando isRound en realidad lleva más tiempo. GollyJer 16/10/20 a las 5:47
  • 2
    Revisé muchas soluciones en StackOverflow y esta es la mejor. La solución de notación exponencial con el mod para números negativos parece funcionar mejor para la moneda y coincide con los cálculos redondos de Java en el backend. Locutus 22 de ene a las 23:30
64

Un método de redondeo preciso. Fuente: 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);
        };
    }
})();

Ejemplos:

// 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
0
62

Ninguna de las respuestas que se encuentran aquí es correcta . @stinkycheeseman pidió redondear , todos redondearon el número.

Para redondear, use esto:

Math.ceil(num * 100)/100;
0
60

A continuación, se muestra una forma sencilla de hacerlo:

Math.round(value * 100) / 100

Sin embargo, es posible que desee seguir adelante y crear una función separada para hacerlo por usted:

function roundToTwo(value) {
    return(Math.round(value * 100) / 100);
}

Entonces simplemente pasaría el valor.

Puede mejorarlo para redondear a cualquier número arbitrario de decimales agregando un segundo parámetro.

function myRound(value, places) {
    var multiplier = Math.pow(10, places);

    return (Math.round(value * multiplier) / multiplier);
}
0
36
+(10).toFixed(2); // = 10
+(10.12345).toFixed(2); // = 10.12

(10).toFixed(2); // = 10.00
(10.12345).toFixed(2); // = 10.12
0
36

Para mí, Math.round () no estaba dando la respuesta correcta. Descubrí que toFixed (2) funciona mejor. A continuación se muestran ejemplos de ambos:

console.log(Math.round(43000 / 80000) * 100); // wrong answer

console.log(((43000 / 80000) * 100).toFixed(2)); // correct answer
1
  • Es importante tener en cuenta que toFixed no realiza un redondeo y que Math.round solo redondea al número entero más cercano. Para conservar los decimales, por lo tanto, debemos multiplicar el número original por el número de potencias de diez cuyos ceros representan el número deseado de decimales y luego dividir el resultado por el mismo número. En su caso: Math.round (43000/80000 * 100 * 100) / 100. Por último, se puede aplicar toFixed (2) para asegurarse de que siempre haya dos decimales en el resultado (con ceros finales cuando sea necesario) - perfecto para alinear a la derecha una serie de números presentados verticalmente :)Turbo 1 de mayo de 2018 a las 14:26
36

Usa esta función Number(x).toFixed(2);

5
  • 9
    Envuélvalo todo Numbernuevamente, si no desea que se devuelva como una cadena:Number(Number(x).toFixed(2));user993683 12 de septiembre de 2015 a las 5:17
  • 5
    La Numberllamada no es necesaria, x.toFixed(2)funciona. bgusach 14 dic 2018 a las 16:09
  • 3
    @bgusach Se necesita una llamada de número, ya que la declaración x.toFixed (2) devuelve una cadena y no un número. Para volver a convertir a un número, necesitamos ajustar con NúmeroMohan Ram 15/03/19 a las 6:26
  • 2
    Cuando se usa este método , se (1).toFixed(2)devuelve 1.00, pero se necesita un interrogador 1en este caso. Eugene Mala 7/06/19 a las 20:40
  • 1
    Esto no funciona, 1.005.toFixed(2)rinde "1"cuando debería "1.01". Adam Jagosz 28/08/19 a las 14:00
34

Pruebe esta solución liviana :

function round(x, digits){
  return parseFloat(x.toFixed(digits))
}

 round(1.222,  2) ;
 // 1.22
 round(1.222, 10) ;
 // 1.222
4
  • ¿Alguien sabe si hay alguna diferencia entre esto y return Number(x.toFixed(digits))? user993683 12 de septiembre de 2015 a las 5:16
  • 1
    @JoeRocc ... no debería hacer ninguna diferencia por lo que puedo ver, ya .toFixed()que solo permite números de todos modos. petermeissner 12/09/15 a las 10:16
  • 4
    Esta respuesta tiene el mismo problema mencionado varias veces en esta página. Intente round(1.005, 2)ver un resultado de en 1lugar de 1.01. MilConDoin 5 de agosto de 2016 a las 6:54
  • parece más un problema del algoritmo de redondeo? - hay más de los que uno podría imaginar: en.wikipedia.org/wiki/Rounding ... round(0.995, 2) => 0.99; round(1.006, 2) => 1.01; round(1.005, 2) => 1petermeissner 7 feb 2018 a las 5:35
33

Esto puede ayudarlo a:

var result = Math.round(input*100)/100;

para obtener más información, puede echar un vistazo a este enlace

Math.round (num) vs num.toFixed (0) e inconsistencias del navegador

1
  • 2
    ¿Por qué en el mundo la respuesta aceptada tiene tantos más votos que esta ya que son prácticamente lo mismo, pero esta se publicó 1 minuto después de la aceptada? DUO Labs 25 de enero de 2020 a las 1:10
32

2017
Solo usa código nativo.toFixed()

number = 1.2345;
number.toFixed(2) // "1.23"

Si necesita ser estricto y agregar dígitos solo si es necesario, puede usar replace

number = 1; // "1"
number.toFixed(5).replace(/\.?0*$/g,'');
6
  • 3
    El método toFixed devuelve una cadena. Si desea un resultado numérico, deberá enviar el resultado de toFixed a parseFloat. Zambonilli 7 de nov. De 2017 a las 20:04
  • @Zambonilli O simplemente multiplique por 1 si es necesario. pero debido a que el número fijo, la mayoría de los casos son para mostrar y no para calcular, la cadena es el formato correctopery mimon 8/11/2017 a las 13:05
  • 2
    -1; no solo fue toFixedsugerido por múltiples respuestas años antes que la suya, sino que no satisface la condición de "solo si es necesario" en la pregunta; (1).toFixed(2)da "1.00"donde el solicitante deseaba "1". Mark Amery 7 dic 2017 a las 0:02
  • Ok lo tengo. También agrego alguna solución para ese casopery mimon 14 dic 2017 a las 17:05
  • Si está usando lodash, es aún más fácil: _.round (number, decimalPlace) Eliminé mi último comentario, porque tiene un problema. Sin embargo, Lodash _.round SÍ funciona. 1.005 con el lugar decimal 2 se convierte en 1.01. Devin Fields 31/07/18 a las 17:40
31

Hay un par de formas de hacerlo. Para gente como yo, la variante de Lodash

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))
}

Uso:

round(0.015, 2) // 0.02
round(1.005, 2) // 1.01

Si su proyecto usa jQuery o lodash, también puede encontrar el roundmétodo adecuado en las bibliotecas.

Actualización 1

Eliminé la variante n.toFixed(2)porque no es correcta. Gracias @ avalanche1

6
  • La segunda opción devolverá una cadena con exactamente dos puntos decimales. La pregunta pide puntos decimales solo si es necesario. La primera opción es mejor en este caso. Marcos Lima 5/07/2016 a las 13:36
  • @MarcosLima Number.toFixed()devolverá una cadena pero con un símbolo más antes, el intérprete de JS convertirá la cadena en un número. Este es un azúcar de sintaxis. stanleyxu2005 5 de julio de 2016 a las 14:04
  • En Firefox, alert((+1234).toFixed(2))muestra "1234.00". Marcos Lima 5 de julio de 2016 a las 14:11
  • En Firefox, alert(+1234.toFixed(2))lanza SyntaxError: identifier starts immediately after numeric literal. Me quedo con la primera opción. Marcos Lima 5 de julio de 2016 a las 15:19
  • Esto no funciona en algunos casos extremos : intente ( jsfiddle ) con 362.42499999999995. Resultado esperado (como en PHP echo round(362.42499999999995, 2)): 362.43. Resultado real:362.42Dr. Gianluigi Zane Zanettini 6 dic 2017 a las 13:42
28

Si está utilizando la biblioteca lodash, puede utilizar el método redondo de lodash como sigue.

_.round(number, precision)

P.ej:

_.round(1.7777777, 2) = 1.78
2
  • @Peter El conjunto de funcionalidades que proporciona Lodash es realmente bueno en comparación con el Javascript estándar. Sin embargo, escuché que Lodash tiene algún problema de rendimiento en comparación con JS estándar. codeburst.io/…Madura Pradeep 4 oct 2018 a las 8:26
  • 2
    Acepto su punto de que existen inconvenientes de rendimiento con el uso de lodash. Creo que esos problemas son comunes a muchas abstracciones. Pero mire cuántas respuestas hay en este hilo y cómo fallan las soluciones intuitivas para los casos extremos. Hemos visto este patrón con jQuery y el problema raíz se resolvió cuando los navegadores adoptaron un estándar común que resolvió la mayoría de nuestros casos de uso. Luego, los cuellos de botella de rendimiento se trasladaron a los motores de los navegadores. Creo que debería pasar lo mismo con lodash. :)Peter 4 oct 2018 a las 10:17
27

Desde ES6 hay una forma 'adecuada' (sin anular las estáticas y crear soluciones alternativas) para hacer esto usando 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));

entonces puede simplemente parseFloaty los ceros "desaparecerán".

console.log(parseFloat((1.4999).toPrecision(3)));
console.log(parseFloat((1.005).toPrecision(3)));
console.log(parseFloat((1.0051).toPrecision(3)));

Sin embargo, no resuelve el 'problema de redondeo de 1.005', ya que es intrínseco a cómo se procesan las fracciones flotantes .

console.log(1.005 - 0.005);

Si está abierto a las bibliotecas, puede usar 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>
7
  • 3
    (1.005).toPrecision(3)todavía regresa en 1.00lugar de en 1.01realidad. Giacomo 23 feb 2018 a las 22:21
  • toPrecisiondevuelve una cadena que cambia el tipo de salida deseado. adamduren 16/11/18 a las 22:59
  • @Giacomo No es una falla de .toPrecisionmétodo, es una especificidad de los números de punto flotante (qué números en JS son) - inténtelo 1.005 - 0.005, regresará 0.9999999999999999. shau-kote 26/11/18 a las 1:44
  • 1
    (1).toPrecision(3)devuelve '1.00', pero el interrogador quería tener 1en este caso. Eugene Mala 7/06/19 a las 20:39
  • 1
    Como dijo @Giacomo, esta respuesta parece confundir "dígitos significativos" con "redondear a varios lugares decimales". toPrecision¿El formato, no el último, y no es una respuesta a la pregunta del OP, aunque puede parecer relevante al principio, se equivoca mucho. Consulte en.wikipedia.org/wiki/Significant_figures . Por ejemplo, Number(123.4).toPrecision(2)devoluciones "1.2e+2"y Number(12.345).toPrecision(2)devoluciones "12". También estoy de acuerdo con el punto de @ adamduren de que devuelve una cadena que no es deseable (no es un gran problema pero no es deseable). Neek 9 de julio de 2019 a las 4:02
24

El enfoque más fácil sería usar toFixed y luego quitar los ceros finales usando la función Number:

const number = 15.5;
Number(number.toFixed(2)); // 15.5
const number = 1.7777777;
Number(number.toFixed(2)); // 1.78
7
  • esto no funciona para todos los casos. Realice pruebas exhaustivas antes de publicar las respuestas. Neeraj 19/04/20 a las 10:23
  • @baburao Publique un caso en el que la solución anterior no funcioneMarcin Wanago 20 abr.20 a las 10:05
  • número constante = 15; Número (number.toFixed (2)); //15.00 en lugar de 15Kevin Jhangiani 23 abr.20 a las 22:10
  • 2
    @KevinJhangiani número constante = 15; Número (number.toFixed (2)); // 15 - Lo probé tanto en la versión más reciente de Chrome como en FirefoxMarcin Wanago 24 abr.20 a las 11:22
  • 1
    Los comentaristas tienen toda la razón, ¡y me di cuenta del error en mi código después de publicar eso! Kevin Jhangiani 26 de mayo a las 4:30 p.m.
23

MarkG y Lavamantis ofrecieron una solución mucho mejor que la aceptada. ¡Es una pena que no obtengan más votos a favor!

Aquí está la función que utilizo para resolver los problemas de decimales de punto flotante también basados ​​en MDN . Es incluso más genérico (pero menos conciso) que la solución de Lavamantis:

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));
}

Úselo con:

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

En comparación con la solución de Lavamantis, podemos hacer ...

round(1234.5678, -2); // Returns 1200
round("123.45");      // Returns 123
2
  • 2
    Su solución no cubre algunos casos a diferencia de la solución de MDN. Si bien puede ser más corto, no es exacto ...astorije 05/07/2015 a las 21:42
  • 1
    redondo (-1835.665,2) => -1835.66Jorge Sampayo 29 de julio de 2016 a las 1:38
20

Puede funcionar para ti

Math.round(num * 100)/100;

para saber la diferencia entre toFixed y round. Puede echar un vistazo a Math.round (num) vs num.toFixed (0) y las inconsistencias del navegador .

17
var roundUpto = function(number, upto){
    return Number(number.toFixed(upto));
}
roundUpto(0.1464676, 2);

toFixed(2) aquí 2 es el número de dígitos hasta el que queremos redondear este número.

1
  • este .toFixed () es más simple de implementar. solo revísalo una vez. Ritesh Dhuri 15 de mayo de 2015 a las 7:19
17

Una forma de lograr tal redondeo solo si es necesario es usar Number.prototype.toLocaleString () :

myNumber.toLocaleString('en', {maximumFractionDigits:2, useGrouping:false})

Esto proporcionará exactamente la salida que espera, pero como cadenas. Aún puede convertirlos nuevamente en números si ese no es el tipo de datos que espera.

2
  • Esta es la solución más limpia que existe y evita todos los problemas complicados de punto flotante, pero el soporte de MDN aún está incompleto: Safari no admite el paso de argumentos toLocaleStringtodavía. Mark Amery 30/07/2016 a las 23:12
  • @MarkAmery Por ahora, solo el navegador de Android tiene algunos problemas: caniuse.com/#search=toLocaleStringptyskju 10/01/20 a las 20:56
16

ACTUALIZACIÓN FINAL:

Dejando esta respuesta aquí para la posteridad, pero recomendaría usar la adaptación de @ AmrAli de la función DecimalPrecision, ya que también está equipada para manejar la notación exponencial. Originalmente había buscado evitar cualquier conversión / manipulación de cadenas de cualquier tipo por razones de rendimiento, pero prácticamente no hay diferencia en el rendimiento con su implementación.

EDITAR 22/8/2020: Creo que debería aclararse aquí que el objetivo de estos esfuerzos no es eliminar por completo los errores de redondeo inherentes causados ​​por el tipo de datos de punto flotante, ya que eso nunca será posible sin cambiar a un tipo de datos que en realidad está almacenando el valor como base10 (decimal). El objetivo realmente debería ser llevar las inexactitudes lo más lejos posible, de modo que pueda realizar operaciones matemáticas en un valor dado sin producir el error. En el momento en que su valor alcanza el límite absoluto, donde simplemente invocar el valor haría que JS produjera el error, ya sea antes o después de haberlo manipulado, no hay nada más que pueda hacer para aliviar eso. Por ejemplo, si crea una instancia del valor 0.014999999999999999, JS lo redondeará inmediatamente a 0.015.Por lo tanto, si pasó ese valor a cualquiera de estas funciones, en realidad está pasando 0.015. En ese punto, ni siquiera podía convertir a una cadena primero y luego manipularlo, el valor tendría que crearse una instancia como una cadena desde el principio para que eso funcione. El objetivo, y la única expectativa razonable, de cualquier función creada para aliviar este error es simplemente permitir que las operaciones matemáticas se realicen en valores de punto flotante mientras empuja el error hasta el borde donde se encuentra el valor inicial o el valor resultante. produciría el error simplemente al ser invocado de todos modos. Las únicas otras soluciones alternativas serían almacenar los números enteros y los valores decimales de forma independiente, ambos como enteros, de modo que solo se invoquen como tales,o almacenar siempre el valor como una cadena y usar una combinación de manipulación de cadenas y matemáticas basadas en números enteros para realizar operaciones en él.

Después de ejecutar varias iteraciones de todas las formas posibles de lograr una precisión real de redondeo decimal precisa, está claro que la solución más precisa y eficiente es utilizar Number.EPSILON. Esto proporciona una verdadera solución matemática al problema de la precisión matemática de coma flotante. Se puede rellenar fácilmente como se muestra aquí: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON para admitir todos los últimos usuarios de IE restantes (de nuevo, tal vez debería dejar de hacer eso).

Adaptado de la solución proporcionada aquí: https://stackoverflow.com/a/48850944/6910392

Una solución sencilla que proporciona redondeo decimal, piso y techo precisos, con una variable de precisión opcional sin agregar una biblioteca completa.

19-05-2020 ACTUALIZACIÓN: Como señaló Sergey en los comentarios, hay una limitación para este (o cualquier otro) método que vale la pena señalar. En el caso de números como 0.014999999999999999, aún experimentará inexactitudes que son el resultado de alcanzar el límite absoluto de las limitaciones de precisión para el almacenamiento de valores de coma flotante. No hay ninguna solución matemática u otra que pueda aplicarse para dar cuenta de eso, ya que el valor en sí se evalúa inmediatamente como 0.015. Puede confirmar esto simplemente invocando ese valor por sí mismo en la consola. Debido a esta limitación, ni siquiera sería posible utilizar la manipulación de cadenas para reducir este valor, ya que su representación de cadenas es simplemente "0.015". Cualquier solución para tener en cuenta esto debería aplicarse de forma lógica en la fuente de los datos antes de aceptar el valor en un script.por ejemplo, restringir la longitud de caracteres de un campo, etc. Esa sería una consideración que debería tenerse en cuenta caso por caso para determinar el mejor enfoque.

19-08-2020 ACTUALIZACIÓN: Según el comentario de Amr, las funciones de techo y piso producirán resultados no deseados cuando el valor de entrada sea un número entero. Esto se debe a la adición aplicada a la entrada con Number.EPSILON para compensar la inexactitud de flotación esperada. La función se ha actualizado para verificar si el valor de entrada es un número entero y devolver el valor sin cambios, ya que ese es el resultado correcto de cualquiera de las funciones cuando se aplica a un número entero.

* Nota: Este problema también revela que si bien las funciones de techo y piso aún requieren la aplicación del ajuste Number.EPSILON, producen resultados no deseados cuando se aplican a un valor donde el número de decimales en el número de entrada es menor que el número de decimales solicitados para la salida (p). Por ejemplo, ceil (17.1, 5) debería devolver 17.1 en relación con el comportamiento esperado de la función "ceil" cuando se aplica a números enteros en matemáticas, donde se supone que todos los lugares decimales después de "1" son 0. Para corregir esto, I ' Agregué una función de verificación adicional para identificar si el número de decimales en el número de entrada es menor que los decimales de salida solicitados, y devuelve el número sin cambios, al igual que con los números enteros.

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));
16
  • 1
    (DecimalPrecision.round (0.014999999999999999, 2)) // devuelve 0.02Sergey 12 de mayo de 2020 a las 14:42
  • ¡Buena atrapada! El problema es con el almacenamiento de punto flotante en JS, siempre habrá algunos casos extremos. La buena noticia es que las matemáticas que aplica a Number.EPSILON primero se pueden ajustar con más precisión para llevar esos casos extremos más allá del límite. Si desea garantizar que no haya posibilidad para casos extremos, su única solución real será la manipulación de cadenas y luego las matemáticas. En el momento en que realiza cualquier cálculo matemático sobre el valor (incluso al intentar mover el decimal), entonces ya ha producido el error. KFish 14 de mayo de 2020 a las 18:42
  • En realidad, en una inspección más detallada, esto no se debe a ninguna de las matemáticas involucradas, sino que el problema se manifiesta inmediatamente al invocar el valor especificado. Puede confirmar esto simplemente escribiendo ese número en la consola y ver que inmediatamente se evalúa como 0.015. Por lo tanto, esto representaría el límite absoluto de precisión para cualquier número de punto flotante en JS. En este caso, ni siquiera podría convertir a una cadena y manipular, ya que el valor de la cadena sería "0.015"KFish 19 de mayo de 2020 a las 1:06
  • 1
    @KFish DecimalPrecision.ceil(17,0); // 18yDecimalPrecision.ceil(17,1); // 17.1Amr Ali 17/08/20 a las 19:56
  • 1
    @KFish DecimalPrecision.ceil(-5.12, 1); // -5.2yDecimalPrecision.floor(-5.12, 1); // -5.1Amr Ali 20/08/20 a las 21:44
14

La manera más fácil:

+num.toFixed(2)

Lo convierte en una cadena y luego de nuevo en un entero / flotante.

3
  • Gracias por esta respuesta más simple. Sin embargo, ¿qué es '+' en + num? No funcionó para mí donde el valor decimal venía en cadena. Lo hice: (num * 1) .toFixed (2). Ethan 22 de marzo de 2015 a las 8:21
  • @momo simplemente cambia el argumento toFixed()a 3. Así sería +num.toFixed(3). Eso está funcionando de la manera que se supone que debe, 1.005 se redondea a 1.00, que es igual a 1bigpotato 4 de mayo de 2015 a las 16:13
  • 1
    @Edmund Se supone que devuelve 1.01, no 1.00mmm 5 de mayo de 2015 a las 19:20
14

Aquí hay un método de prototipo:

Number.prototype.round = function(places){
    places = Math.pow(10, places); 
    return Math.round(this * places)/places;
}

var yournum = 10.55555;
yournum = yournum.round(2);
13

Use algo como esto "parseFloat (parseFloat (value) .toFixed (2))"

parseFloat(parseFloat("1.7777777").toFixed(2))-->1.78 
parseFloat(parseFloat("10").toFixed(2))-->10 
parseFloat(parseFloat("9.1").toFixed(2))-->9.1
1
  • 1
    no si la inexactitud es intrínseca a la representación flotante. ¡simplemente lo eliminaría y luego volvería a introducir el mismo error al convertir nuevamente a flotar nuevamente! Ben McIntyre 28 oct 2018 a las 20:00