Redondear a lo más 2 decimales (solo si es necesario)

2759

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

Entrada:

10
1.7777777
9.1

Salida:

10
1.78
9.1

¿Cómo puedo hacer esto en JavaScript?

apestoso
fuente
22
Hice un violín con muchas de las técnicas que se ofrecen como soluciones aquí ... para que pueda comparar: violín
dsdsdsdsd
37
Nadie parece estar al tanto Number.EPSILON. Uso Math.round( num * 100 + Number.EPSILON ) / 100.
cronvel
3
Para los nuevos lectores, no puede hacer esto a menos que tenga un resultado de tipo cadena . Las matemáticas de coma flotante en representaciones internas binarias de números significan que siempre hay números que no se pueden representar como decimales claros.
Walf
99
@cronvel ¿Puedes explicar la razón de usar Number.EPSILONaquí?
Bruce Sun
55
Me caí por la madriguera del conejo y probé algunas de las respuestas más interesantes en esta página (solo la primera página). Aquí hay un Codepen . Sugerencia: cuanto más votos positivos tenga la respuesta, menores serán las posibilidades de que funcione correctamente.
Adam Jagosz

Respuestas:

3495

Utilizar Math.round(num * 100) / 100

Editar: para garantizar que cosas como 1.005 redondeen correctamente, utilizamos

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

Brian Ustas
fuente
395
Si bien esto funcionará para la mayoría de los casos, no funcionará para 1.005, que terminará siendo 1 en lugar de 1.01
James
83
@ James Wow, eso es realmente extraño: estoy trabajando en la consola de desarrollo de Chrome y me doy cuenta de que 1.005 * 100 = 100.49999999999999. Math.round (100.49999999999999) se evalúa a 100, mientras que Math.round (100.5) se evalúa a 101. IE9 hace lo mismo. Esto se debe a la rareza de coma flotante en javascript
stinkycheeseman
153
Una solución simple. Para 2 dp, use Math.round((num + 0.00001) * 100) / 100. Probar Math.round((1.005 + 0.00001) * 100) / 100yMath.round((1.0049 + 0.00001) * 100) / 100
mrkschan
31
@mrkschan ¿Por qué funciona eso, y eso es infalible para todos los números?
CMCDragonkai
86
Para aquellos de ustedes que no lo entienden, esta técnica se llama escalamiento. Básicamente, lo que la respuesta hace aquí es traer dos figuras a través del punto decimal convirtiendo la figura en un número entero para evitar todos los problemas de coma flotante, redondea eso y luego traduce de nuevo a lo que era antes dividiendo por 100 y tienes tu responde a 2dp.
Alex_Nabu
3064

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 () 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 flotante 1.55499994 detrás de escena.

La solución 1 es usar 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. Existen 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 frontales y extraer valores redondeados del servidor de fondo.

Un kunin
fuente
81
Este enfoque (toFixed) es bueno y funcionó para mí, pero específicamente no cumple con la solicitud original de "solo cuando es necesario". (Redondea 1.5 a 1.50, lo que rompe la especificación.)
Según Lundberg el
29
Para el requisito "cuando sea necesario", haga esto: parseFloat(number.toFixed(decimalPlaces)); @PerLundberg
Onur Yıldırım
36
parseFloat("55.555").toFixed(2)vuelve "55.55"en la consola de desarrollo de Chrome.
Levi Botelho
22
No hay ninguna ventaja de usar toFixed en lugar de Math.round; toFixed conduce a los mismos problemas de redondeo (pruébelos con 5.555 y 1.005), pero es como 500x (no es broma) más lento que Math.round ... Parece que la respuesta de @MarkG es la más precisa aquí.
Pierre
17
toFixed no "a veces" devuelve una cadena, siempre devuelve una cadena.
McGuireV10
464

Puedes usar

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

Encontré esto en MDN . Su manera 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
MarkG
fuente
13
@Redsandro, +(val)es el equivalente de coerción del uso Number(val). Concatenar "e-2" a un número resultó en una cadena que necesitaba ser convertida de nuevo a un número.
Jack
41
Tenga en cuenta que para flotadores grandes y pequeños que producirían NaN ya que, por ejemplo, + "1e-21 + 2" no se analizarán correctamente.
Pierre
16
Usted 'resolvió' el 'problema' 1.005, pero introdujo uno nuevo: ahora, en la consola Chrome, roundToTwo(1.0049999999999999)aparece como 1.01 (inevitablemente, desde entonces 1.0049999999999999 == 1.005). Me parece que el flotante que obtienes si escribes num = 1.005'obviamente' 'debería' redondearse a 1.00, porque el valor exacto de num es menor que 1.005. Por supuesto, también me parece que la cadena '1.005' 'obviamente' 'debería' redondearse a 1.01. El hecho de que diferentes personas parezcan tener diferentes intuiciones sobre cuál es el comportamiento correcto real aquí es parte de por qué es complicado.
Mark Amery
35
No hay un número (punto flotante) en el medio 1.0049999999999999y 1.005, por definición, son el mismo número. Esto se llama un corte dedekind.
Azmisov
66
@Azmisov tiene razón. Mientras 1.00499 < 1.005es true, 1.0049999999999999 < 1.005evalúa a false.
falconepl
146

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');
  });
})
Lavamantis
fuente
20
Pierre planteó un problema grave con la respuesta de MarkG.
dsjoerg
99
Nota: Si no desea alterar el prototipo Number.prototype, simplemente escriba esto como una función: function round(number, decimals) { return +(Math.round(number + "e+" + decimals) + "e-" + decimals); }
Philipp Tsipman
2
Una forma genial de expandir esto. Puede verificar si el decimal es negativo y luego invertir e + y e-. Entonces n = 115; n.round (-2); produciría 100
Lee Louviere
3
Prueba n: 1e + 19 - devuelve NaN
DavidJ
3
Este algoritmo siempre se redondea (en lugar de alejarse de cero). Entonces, en caso de números negativos, la salida no es lo que cabría esperar:(-1.005).round(2) === -1
Aleksej Komarov
123

Deberías usar:

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

Nadie parece ser consciente de Number.EPSILON .

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

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

Aquí vienen 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 preguntó en los comentarios, aclaremos una cosa: agregar Number.EPSILONes relevante solo cuando el valor a redondear es el resultado de una operación aritmética, ya que puede tragar algún error de coma flotante delta.

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, utilizo una función similar a esta para comparar números con epsilon:

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 afirmación + validación de datos lib 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 como para acumular un error de coma flotante.

Hasta ahora, se ve perfecto para mí. Espero que sea de ayuda.

cronvel
fuente
1
@palota, podría definir un número de púas como el que menciona EPSILON cronvel
Daniel San
3
¡Esta es la respuesta correcta! El problema en sí está relacionado con el funcionamiento interno de los números flotantes, no con JavaScript
Daniel San
22
En realidad, debe agregar epsilon ANTES de multiplicar por 100 Math.round( (num + Number.EPSILON) * 100) / 100.. También estoy de acuerdo en que este es el método correcto para redondear correctamente (aunque no es exactamente lo que se preguntó en esta pregunta).
maganap
2
@cronvel Hmm. Tienes razón; la elección de la dirección tiene sentido. Entonces, supongo que mi objeción restante es que hacer esto solo tiene sentido si está trabajando en un dominio en el que tiene una razón de principios para pensar que su valor es un "número redondo". Si sabe que su entrada es el resultado de operaciones aritméticas simples en números con pequeños números de decimales, entonces seguramente, solo tiene 0.004999999999999999como resultado un error de coma flotante compuesto y el resultado matemáticamente correcto fue probablemente 0.005. Si es una lectura de un sensor? No tanto.
Mark Amery
1
Se @marchaos no falla: mitades siempre se redondean hacia arriba . Por ejemplo, Math.round(1.5)= 2, pero Math.round(-1.5)= -1. Entonces esto es perfectamente consistente. Aquí -1 es mayor que -2, al igual que -1000 es mayor que -1000.01. No debe confundirse con mayores números absolutos .
cronvel
84

Se puede usar .toFixed(NumberOfDecimalPlaces).

var str = 10.234.toFixed(2); // => '10.23'
var number = Number(str); // => 10.23
Gourav Singla
fuente
44
También porque agrega ceros al final, que no es lo que pidió la pregunta original .
Alastair Maw
2
Pero los ceros finales se eliminan fácilmente con una expresión regular, es decir, `Número (10.10000.toFixed (2) .replace (/ 0 + $ /, ''))` => 10.1
Chad
1
@daniel la respuesta principal es la misma (a partir de ahora) pero no siempre se redondea correctamente, intente +(1.005).toFixed(2)cuál devuelve en 1lugar de 1.01.
Emile Bergeron el
3
@ChadMcElligott: su expresión regular no funciona bien con enteros: Number(9).toFixed(2).replace(/0+$/, '')=> "9."
Jacob van Lingen
Parece que esto no se redondea. Simplemente se trunca básicamente. En algunos casos, lo suficientemente bien! Gracias.
iedmrc
78

Esta pregunta es complicada.

Supongamos que tenemos una función, roundTo2DP(num)que toma un flotador como argumento y devuelve un valor redondeado a 2 decimales. ¿Qué debe 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 redondear a 0.01 (porque está más cerca de 0.01 que a 0.02) mientras que los otros dos deberían redondear a 0.02 (porque 0.0150000000000000001 está más cerca de 0.02 que a 0.01, y porque 0.015 está exactamente a mitad de camino entre ellos y hay una convención matemática de que tales 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 no 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, como sucede, 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, en el shell de nodo u otro intérprete de JavaScript. Solo compáralos:

> 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 una cadena ...

> 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 redondear a 0.02, y que notablemente no es el número de 56 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 número entero s cuyos dígitos se utilizarán en la representación de cadena de m :

supongamos que n , k y s son números enteros tales que k ≥ 1, 10 k -1s <10 k , el valor de Número para s × 10 n - k es m , y k es lo más pequeño posible. Tenga en cuenta que k es el número de dígitos en la representación decimal de s , que s no es divisible por 10, y que el dígito menos significativo de s no está necesariamente determinado únicamente por estos criterios.

La parte clave aquí es el requisito de que " k es lo más pequeño posible". Lo que equivale a ese requisito es un requisito que, dado un Número m, el valor de String(m)debe tener el menor número posible de dígitos sin dejar de cumplir el requisito de que Number(String(m)) === m. Como ya sabemos eso 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 lo que roundTo2DP(m) debería regresar. Si mel valor exacto es 0.01499999999999999944488848768742172978818416595458984375, pero su representación de cadena es '0.015', entonces cuál es el correcto respuesta , matemática, práctica, filosófica o lo que sea, cuando redondeamos a dos decimales?

No hay una única respuesta correcta a esto. Depende de su caso de uso. Probablemente desee respetar la representación de cadena 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 verdadero 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 binarios de coma flotante 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 punto flotante binario más cercano.

Por otro lado, es probable que desee respetar el valor de punto flotante binario y redondear hacia abajo cuando su valor proviene 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 la cadena del número, podemos (con un poco de código razonablemente sutil) implementar nuestro propio redondeo que actúa directamente sobre la representación de la cadena, dígito a dígito, utilizando el mismo algoritmo que hubiera 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 a 2 decimales "solo cuando sea necesario" quitando los ceros finales después del punto decimal; puede, por supuesto, necesitar 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+$/, '')
}

Ejemplo de uso:

> 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 lo que desea usar para evitar que los usuarios sean testigos de que los números que ingresaron se redondearon incorrectamente.

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

Pero, ¿qué sucede 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 decimales son más precisas que las que tienen más? En ese caso, no queremos respetar la representación de cadena, porque esa representación (como se explica en la especificación) ya está redondeada; no queremos cometer el error de decir "0.014999999 ... 375 redondea a 0.015, que redondea a 0.02, entonces 0.014999999 ... 375 redondea a 0.02".

Aquí podemos simplemente usar el toFixedmétodo incorporado . Tenga en cuenta que al invocar Number()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, discutido 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);
}
Mark Amery
fuente
Esto no funciona en algunos casos extremos : pruebe ( jsfiddle ) con roundStringNumberWithoutTrailingZeroes(362.42499999999995, 2). Resultado esperado (como en PHP echo round(362.42499999999995, 2)): 362.43. Resultado real:362.42
Dr. Gianluigi Zane Zanettini
1
@ Dr.GianluigiZaneZanettini Huh. Extraño. No estoy seguro de por qué PHP roundda 362.43. Eso parece intuitivamente incorrecto, ya que 362.42499999999995 es inferior a 362.425 (en matemáticas y en código, 362.42499999999995 < 362.425es cierto tanto en JS como en PHP). La respuesta de PHP tampoco minimiza la distancia entre los números de coma flotante originales y redondeados, desde entonces 362.43 - 362.42499999999995 > 362.42499999999995 - 362.42. De acuerdo con php.net/manual/en/function.round.php , PHP roundsigue el estándar C99; Tendré que aventurarme en la tierra de C para entender lo que está sucediendo.
Mark Amery
1
Después de haber examinado la implementación del redondeo en la fuente de PHP , no tengo ni idea de lo que está haciendo. Es una implementación horriblemente complicada llena de macros y ramas y conversiones de cadenas y ramificaciones en valores de precisión mágicos codificados. No estoy seguro de qué decir más allá de "la respuesta de PHP es descaradamente incorrecta, y deberíamos presentar un informe de error". ¿Cómo encontraste el número 362.42499999999995, por cierto?
Mark Amery
1
@ Dr.GianluigiZaneZanettini He creado un informe de error: bugs.php.net/bug.php?id=75644
Mark Amery
2
@ Dr.GianluigiZaneZanettini "¿Tengo algún sentido?" - No; así no es como funciona el redondeo. 1.000005 termina en cinco, pero si se redondea al entero más cercano, la respuesta debe ser 1, no 2. Del mismo modo, 1499995 termina en cinco, pero si se redondea al millón más cercano, el resultado debería ser 1000000, no 2000000. En el caso de 362.42499999999995 redondeado a 2 DP, lo que debería determinar la dirección de redondeo es el tercer decimal, que es 4.
Mark Amery
76

Considere .toFixed()y .toPrecision():

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

AceCorban
fuente
1
toFixed agrega los puntos decimales a cada valor sin importar qué.
stinkycheeseman
13
Ambos son inútiles aquí
Esailija
Desafortunadamente, ambas funciones agregarán lugares decimales adicionales que @stinkycheeseman parece no querer.
Jackwanders
2
devuelven cadenas y no números, por lo que están formateando y no calculando ..
saimiris_devel
63

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
usuario
fuente
Alguien puso esto en GitHub y npm también: github.com/jhohlfeld/round10
Jo Liss
No, Math.round10(3544.5249, -2)devuelve 3544.52 en lugar de 3544.53
Matija
2
@Matija Wolfram alpha también dice 3544.52. Desea minimizar el error entre el número actual y la aproximación redondeada. La aproximación más cercana 3544.5249a 2 decimales es 3544.52(error = 0.0049). Si lo fuera 3544.53, el error sería 0.0051. Está realizando redondeos sucesivos, es decir, Math.round10 (Math.round10 (3544.5249, -3), -2), lo que da un error de redondeo mayor y, por lo tanto, no es deseable.
usuario
3
@Matija, desde el punto de vista matemático, 3544.5249 redondeado es 3544.52, no 3544.53, por lo que este código es correcto. Si desea redondearlo a 3544.53 en este caso y en casos como este (incluso difícil es incorrecto), haga algo como esto:number += 0.00011
Bozidar Sikanjic
@Matija: Creo que la función funciona como debería. Tal vez usted quiere iterar el redondeo como:Math.round10( Math.round10(3544.5249, -3) , -2)
jumxozizi
60

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

Para redondear, use esto:

Math.ceil(num * 100)/100;
máquina
fuente
15
El ejemplo de entrada y salida muestra que aunque la pregunta decía 'redondear ...' en realidad estaba destinado a ser 'redondear a ...'.
JayDM
2
@stinkycheeseman estaba señalando un error en un caso específico, no quería redondear siempre como lo hace el techo, solo quería que 0.005 redondeara a 0.01
mjaggard
99
Se encontraron errores extraños durante las pruebas: Math.ceil(1.1 * 100)/100;regresa 1.11, porque 1.1 * 100 está de 110.00000000000001acuerdo con los nuevos navegadores modernos Firefox, Chrome, Safari y Opera ... IE, a la antigua usanza, todavía piensa 1.1*100=1100.
skobaljic
1
@skobaljic tryMath.ceil(num.toFixed(4) * 100) / 100
treeface
1
@treeface Math.ceil((1.1).toFixed(4) * 100) / 100también regresará 1.11en Firefox, el problema / error de los navegadores modernos es la multiplicación y la gente debería saberlo (por ejemplo, trabajé en un juego de lotería esa vez).
skobaljic
47

Aquí hay una manera simple de hacerlo:

Math.round(value * 100) / 100

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

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

Entonces simplemente pasarías 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);
}
JayDM
fuente
2
Esta solución es incorrecta, consulte stackoverflow.com/questions/38322372/… si ingresa 156893.145 y lo redondea con la función anterior, ¡obtiene 156893.14 en lugar de 156893.15!
saimiris_devel
2
@saimiris_devel, ha logrado encontrar una instancia donde falla la representación numérica de números en JavaScript. Tienes razón en que se redondea incorrectamente. Pero eso se debe a que su número de muestra cuando se multiplica por 100 ya está roto (15689314.499999998). Realmente, una respuesta con todas las funciones requeriría una biblioteca que se haya desarrollado específicamente para tener en cuenta las inconsistencias en el manejo de números reales por parte de JavaScript. De lo contrario, probablemente podría invalidar cualquiera de las respuestas que se han dado a esta pregunta.
JayDM
36
+(10).toFixed(2); // = 10
+(10.12345).toFixed(2); // = 10.12

(10).toFixed(2); // = 10.00
(10.12345).toFixed(2); // = 10.12
usuario3711536
fuente
1
Esto no siempre dará los mismos resultados que obtendría si tomara la representación de cadena de su número y la redondeara. Por ejemplo, +(0.015).toFixed(2) == 0.01.
Mark Amery
35

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

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

console.log(((43000 / 80000) * 100).toFixed(2)); // correct answer

Vikasdeep Singh
fuente
Es importante tener en cuenta que toFixed no realiza un redondeo, y que Math.round simplemente se redondea al número entero más cercano. Para preservar 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. Finalmente, se puede aplicar toFixed (2) para garantizar 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
77
También es importante tener en cuenta que .toFIxed () genera una cadena, no un número.
carpiediem
2
Esto todavía no resuelve el redondeo 1.005. (1.005).toFixed(2)todavía resulta a 1.00.
DPac
34

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

Harish.bazee
fuente
8
Envuélvalo todo de Numbernuevo, si no desea que se devuelva como una cadena:Number(Number(x).toFixed(2));
55
La Numberllamada no es necesaria, x.toFixed(2)funciona.
bgusach
3
@bgusach Se necesita una llamada de número, ya que la instrucción x.toFixed (2) devuelve una cadena y no un número. Para convertir de nuevo al número que necesitamos para envolver con Número
Mohan Ram
2
Cuando se utiliza este método (1).toFixed(2)regresa 1.00, pero se necesita un interrogador 1en este caso.
Eugene Mala
1
Esto no funciona, 1.005.toFixed(2)rinde "1"cuando debería ser "1.01".
Adam Jagosz
33

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,'');
pery mimon
fuente
3
El método toFixed devuelve una cadena. Si desea un resultado numérico, deberá enviar el resultado de toFixed a parseFloat.
Zambonilli
@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 correcto
pery mimon
2
-1; no solo fue toFixedsugerido por múltiples respuestas años antes que la suya, sino que no cumple con la condición de "solo si es necesario" en la pregunta; (1).toFixed(2)da "1.00"donde el autor de la pregunta deseaba "1".
Mark Amery
Ok lo tengo. Agrego alguna solución también para ese caso
pery mimon
Si está usando lodash, es aún más fácil: _.round (number, decimalPlace) Eliminé mi último comentario, porque tiene un problema. Lodash _.round sí funciona, sin embargo. 1.005 con lugar decimal de 2 se convierte a 1.01.
Devin Fields
32

Prueba este peso ligero solución :

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

 round(1.222,  2) ;
 // 1.22
 round(1.222, 10) ;
 // 1.222
Petermeissner
fuente
Alguien sabe si hay alguna diferencia entre esto y return Number(x.toFixed(digits))?
1
@JoeRocc ... no debería hacer ninguna diferencia por lo que puedo ver, ya .toFixed()que solo permite números de todos modos.
petermeissner
44
Esta respuesta tiene el mismo problema que se menciona varias veces en esta página. Intenta round(1.005, 2)ver un resultado de en 1lugar de 1.01.
MilConDoin
Parece más un problema del redondeo algo? - hay más de lo 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) => 1
petermeissner
31

Hay un par de formas de hacerlo. Para personas 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

stanleyxu2005
fuente
La segunda opción devolverá una cadena con exactamente dos puntos decimales. La pregunta solicita puntos decimales solo si es necesario. La primera opción es mejor en este caso.
Marcos Lima
@MarcosLima Number.toFixed()devolverá una cadena pero con un símbolo más antes, el intérprete JS convertirá la cadena en un número. Esta es una sintaxis de azúcar.
stanleyxu2005
En Firefox, alert((+1234).toFixed(2))muestra "1234.00".
Marcos Lima
En Firefox, alert(+1234.toFixed(2))lanza SyntaxError: identifier starts immediately after numeric literal. Me quedo con la primera opción.
Marcos Lima
Esto no funciona en algunos casos extremos : pruebe ( jsfiddle ) con 362.42499999999995. Resultado esperado (como en PHP echo round(362.42499999999995, 2)): 362.43. Resultado real:362.42
Dr. Gianluigi Zane Zanettini
26

Si está usando la biblioteca lodash, puede usar el método redondo de lodash como el siguiente.

_.round(number, precision)

P.ej:

_.round(1.7777777, 2) = 1.78
Madura Pradeep
fuente
@Peter El conjunto de funcionalidades que proporciona Lodash es realmente bueno en comparación con Javascript estándar. Sin embargo, escuché que Lodash tiene algunos problemas de rendimiento con respecto a JS estándar. codeburst.io/…
Madura Pradeep
1
Acepto su punto de que existen inconvenientes de rendimiento con el uso de lodash. Creo que esos problemas son comunes a muchas abstracciones. Pero solo mire cuántas respuestas hay en este hilo y cómo las soluciones intuitivas fallan en 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. Los cuellos de botella de rendimiento se trasladaron a los motores del navegador. Creo que lo mismo debería pasarle a Lodash. :)
Peter
26

Desde ES6 hay una forma 'adecuada' (sin anular las estadísticas y crear soluciones) 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 puedes 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 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>

Matas Vaitkevicius
fuente
3
(1.005).toPrecision(3)Todavía regresa en 1.00lugar de en 1.01realidad.
Giacomo
toPrecisiondevuelve una cadena que cambia el tipo de salida deseado.
adamduren
@Giacomo No es un defecto del .toPrecisionmétodo, es una especificidad de los números de coma flotante (que son los números en JS) - intente 1.005 - 0.005, volverá 0.9999999999999999.
shau-kote
1
(1).toPrecision(3)devuelve '1.00', pero el interlocutor quería tenerlo 1en este caso.
Eugene Mala
1
Como dijo @Giacomo, esta respuesta parece confundir "dígitos significativos" con "redondear a un número de decimales". toPrecisionhace el formato, no el último, y no es una respuesta a la pregunta del OP, aunque al principio parezca relevante, se equivoca mucho. Ver 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 deseable).
Neek
23

MarkG y Lavamantis ofrecieron una solución mucho mejor que la que ha sido aceptada. ¡Es una pena que no reciban más votos!

Aquí está la función que uso para resolver los problemas de decimales de punto flotante también basados ​​en MDN . Es aún 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
astorije
fuente
2
Su solución no cubre algunos casos en oposición a la solución de MDN. Si bien puede ser más corto, no es preciso ...
astorije
1
ronda (-1835.665,2) => -1835.66
Jorge Sampayo
21

Esto puede ayudarlo a:

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

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

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

totten
fuente
1
¿Por qué en el mundo la respuesta aceptada tiene tantos votos más que esta ya que son prácticamente lo mismo, pero esta se publicó 1 minuto después de la aceptada?
Cita Dave
18

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

const number = 15.5;
Number(number.toFixed(2)); // 15.5
const number = 1.7777777;
Number(number.toFixed(2)); // 1.78
Marcin Wanago
fuente
Esto no funciona para todos los casos. hacer pruebas exhaustivas antes de publicar respuestas.
baburao
@baburao Por favor, publique un caso en el que la solución anterior no funcione
Marcin Wanago
número constante = 15; Number (number.toFixed (2)); //15.00 en lugar de 15
Kevin Jhangiani
1
@KevinJhangiani const número = 15; Number (number.toFixed (2)); // 15 - Lo probé en Chrome y Firefox más nuevos
Marcin Wanago
@KevinJhangiani, ¿cómo te va 15.00? Los números en JS no almacenan los lugares decimales y cualquier pantalla trunca automáticamente el exceso de lugares decimales (cualquier cero al final).
VLAZ
16
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.

Ritesh Dhuri
fuente
Este .toFixed () es más sencillo de implementar. solo hazlo una vez.
Ritesh Dhuri
14

La manera más fácil:

+num.toFixed(2)

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

papa grande
fuente
Gracias por esta respuesta más simple. Sin embargo, ¿qué es '+' en + num? No funcionó para mí donde el valor decimal vino en cadena. Lo hice: (num * 1) .toFixed (2).
Ethan
@momo simplemente cambia el argumento toFixed()a 3. Así sería +num.toFixed(3). Eso está funcionando de la manera en que se supone que es, 1.005 se redondea a 1.00, que es igual a 1
bigpotato
1
@Edmund Se supone que devuelve 1.01, no 1.00
mmm
13

Aquí hay un método 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);
arielf
fuente
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
Arulraj
fuente
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 volviendo a convertirlo en flotante!
Ben McIntyre
12

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

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

Esto proporcionará exactamente el resultado que espera, pero como cadenas. Todavía puede convertirlos de nuevo en números si ese no es el tipo de datos que espera.

Javarome
fuente
Esta es la solución más limpia que existe y evita todos los problemas complicados de coma flotante, pero el soporte por MDN aún está incompleto: Safari aún no admite pasar argumentos toLocaleString.
Mark Amery
@MarkAmery Por ahora, solo el navegador de Android tiene algunos problemas: caniuse.com/#search=toLocaleString
ptyskju
12

Después de ejecutar varias iteraciones de todas las formas posibles para lograr una precisión de redondeo decimal verdadera y precisa, está claro que la solución más precisa y eficiente es usar 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 a todos los últimos usuarios restantes de IE (quizás nuevamente debería dejar de hacer eso).

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

Una solución simple de caída que proporciona redondeo decimal preciso, piso y techo, con una variable de precisión opcional sin agregar una biblioteca completa.

ACTUALIZACIÓN: Como Sergey señaló en los comentarios, hay una limitación para este (o cualquier) método que valga la pena señalar. En el caso de números como 0.014999999999999999, seguirá experimentando imprecisiones 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 matemática u otra solución que se pueda aplicar para dar cuenta de eso, ya que el valor en sí mismo 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 explicar esto debería aplicarse lógicamente en la fuente de los datos antes de aceptar el valor en un script,

var DecimalPrecision = (function(){
        if (Number.EPSILON === undefined) {
            Number.EPSILON = Math.pow(2, -52);
        }
        this.round = function(n, p=2){
            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){
            let r = 0.5 * Number.EPSILON * n;
            let o = 1; while(p-- > 0) o *= 10;
            if(n < 0)
                o *= -1;
            return Math.ceil((n + r) * o) / o;
        }
        this.floor = function(n, p=2){
            let r = 0.5 * Number.EPSILON * n;
            let o = 1; while(p-- > 0) o *= 10;
            if(n < 0)
                o *= -1;
            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));

KFish
fuente
1
(DecimalPrecision.round (0.014999999999999999, 2)) // devuelve 0.02
Sergey
¡Buena atrapada! El problema es con el almacenamiento de punto flotante en JS, siempre habrá algunos casos extremos. La buena noticia es que la matemática que aplica a Number.EPSILON primero se puede ajustar con mayor precisión para empujar esos casos extremos más allá del borde. Si no quiere garantizar la posibilidad de 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), ya ha producido el error.
KFish
En realidad, en una inspección posterior, 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 a 0.015. Por lo tanto, esto representaría el borde absoluto de precisión para cualquier número de coma flotante en JS. En este caso, ni siquiera podría convertir a cadena y manipular ya que el valor de la cadena sería "0.015"
KFish
11

Esta es la solución más simple y elegante (y yo soy el mejor del mundo):

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
Soldeplata Saketos
fuente
44
Esa es una buena manera de reescribir la respuesta aceptada para aceptar un argumento usando Enotación.
AxelH
1
Esto no funciona en algunos casos extremos : pruebe ( jsfiddle ) roundToX(362.42499999999995, 2). Resultado esperado (como en PHP echo round(362.42499999999995, 2)): 362.43. Resultado real:362.42
Dr. Gianluigi Zane Zanettini
66
En mi humilde opinión, su resultado PHP es incorrecto. No importa lo que venga después del tercer decimal, si el tercer decimal es menor que 5, entonces el segundo decimal debería permanecer igual. Esa es la definición matemática.
Soldeplata Saketos
1
Para ser aún más conciso, "e +" puede ser simplemente "e".
Lonnie Best