¿Cómo lidiar con la precisión del número de coma flotante en JavaScript?

620

Tengo el siguiente script de prueba ficticio:

function test() {
  var x = 0.1 * 0.2;
  document.write(x);
}
test();

Esto imprimirá el resultado, 0.020000000000000004mientras que solo debería imprimir 0.02(si usa su calculadora). Según tengo entendido, esto se debe a errores en la precisión de multiplicación de coma flotante.

¿Alguien tiene una buena solución para que en tal caso obtenga el resultado correcto 0.02? Sé que hay funciones como toFixedo el redondeo sería otra posibilidad, pero me gustaría tener el número entero impreso sin ningún corte y redondeo. Solo quería saber si alguno de ustedes tiene una solución agradable y elegante.

Por supuesto, de lo contrario redondearé a unos 10 dígitos más o menos.

Juri
fuente
118
En realidad, el error se debe a que no hay forma de asignar 0.1a un número finito de punto flotante binario.
Aaron Digulla
10
La mayoría de las fracciones no se pueden convertir a un decimal con precisión exacta. Una buena explicación está aquí: docs.python.org/release/2.5.1/tut/node16.html
Nate Zaugg
77
posible duplicado de ¿Está rota la matemática de JavaScript?
epascarello
53
@SalmanA: Que tu tiempo de ejecución de JavaScript te oculte este problema no significa que esté equivocado.
Aaron Digulla
55
No estoy de acuerdo con Aaron, hay formas de codificar 0.1 perfecta y completamente en binario. Pero IEEE 754 no necesariamente define esto. Imagine una representación en la que codificaría la parte entera en binario, por un lado, la parte decimal por otro lado, hasta n decimales, también en binario, como un entero normal> 0, y finalmente, la posición del punto decimal . Bueno, representaría 0.1 perfectamente, sin error. Por cierto, dado que JS usa un número finito de decimales internamente, los desarrolladores también podrían codificar las agallas para no cometer ese error en los últimos decimales.
Fabien Haddadi

Respuestas:

469

De la guía de punto flotante :

¿Qué puedo hacer para evitar este problema?

Eso depende de qué tipo de cálculos estés haciendo.

  • Si realmente necesita que sus resultados se sumen exactamente, especialmente cuando trabaja con dinero: use un tipo de datos decimal especial.
  • Si simplemente no desea ver todos esos decimales adicionales: simplemente formatee su resultado redondeado a un número fijo de decimales cuando lo muestre.
  • Si no tiene disponible ningún tipo de datos decimales, una alternativa es trabajar con enteros, por ejemplo, hacer cálculos de dinero completamente en centavos. Pero esto es más trabajo y tiene algunos inconvenientes.

Tenga en cuenta que el primer punto solo se aplica si realmente necesita un comportamiento decimal preciso específico . La mayoría de las personas no necesitan eso, simplemente están molestos porque sus programas no funcionan correctamente con números como 1/10 sin darse cuenta de que ni siquiera parpadearían ante el mismo error si ocurriera con 1/3.

Si el primer punto realmente se aplica a usted, use BigDecimal para JavaScript , que no es elegante en absoluto, pero en realidad resuelve el problema en lugar de proporcionar una solución imperfecta.

Michael Borgwardt
fuente
11
Noté su vínculo muerto para BigDecimal y, mientras buscaba un espejo, encontré una alternativa llamada BigNumber: jsfromhell.com/classes/bignumber
Jacksonkr
44
@ bass-t: Sí, pero los flotantes pueden representar exactamente números enteros hasta la longitud del significado, y según el estándar ECMA es un flotante de 64 bits. Por lo tanto, puede representar números enteros exactamente hasta 2 ^ 52
Michael Borgwardt
55
@Karl: La fracción decimal 1/10 no se puede representar como una fracción binaria finita en la base 2, y eso es lo que son los números de Javascript. Por lo tanto, es de hecho exactamente el mismo problema.
Michael Borgwardt
12
Hoy aprendí que incluso los enteros tienen problemas de precisión en JavaScript. Tengamos en cuenta que console.log(9332654729891549)en realidad impresiones 9332654729891548(es decir, fuera por uno!)
mlathe
12
@mlathe: Doh .. ;P... Entre 2⁵²= 4,503,599,627,370,496y 2⁵³= 9,007,199,254,740,992los números representables son exactamente los enteros . Para el siguiente rango, de 2⁵³a 2⁵⁴, todo se multiplica por2 , por lo que los números representables son los pares , etc. Por el contrario, para el rango anterior de 2⁵¹a 2⁵², el espaciado es 0.5, etc. Esto se debe simplemente a aumentar | disminuir la base | radix 2 | exponente binario en / del valor flotante de 64 bits (que a su vez explica el comportamiento 'inesperado' raramente documentado de toPrecision()para valores entre 0y 1).
GitaarLAB
126

Me gusta la solución de Pedro Ladaria y uso algo similar.

function strip(number) {
    return (parseFloat(number).toPrecision(12));
}

A diferencia de la solución de Pedros, esto redondeará 0.999 ... repitiendo y es exacto a más / menos uno en el dígito menos significativo.

Nota: Cuando se trata de flotantes de 32 o 64 bits, debe usar toPrecision (7) y toPrecision (15) para obtener mejores resultados. Vea esta pregunta para obtener información sobre por qué.

linux_mike
fuente
21
¿Alguna razón por la que elegiste 12?
qwertymk
18
toPrecisiondevuelve una cadena en lugar de un número. Esto puede no ser siempre deseable.
SStanley
77
parseFloat (1.005) .toPrecision (3) => 1.00
Peter
55
@ user2428118, lo sé, quise mostrar el error de redondeo, el resultado es 1.00 en lugar de 1.01
Peter
99
Lo que dijo @ user2428118 puede no ser lo suficientemente obvio: (9.99*5).toPrecision(2)= 50 en lugar de 49.95 porque toPrecision cuenta el número entero, no solo decimales. Luego puede usar toPrecision(4), pero si su resultado es> 100, entonces no tendrá suerte nuevamente, ya que permitirá los primeros tres números y un decimal, de esa manera desplazará el punto y lo hará más o menos inutilizable. Terminé usando en su toFixed(2)lugar
aexl
79

Para los matemáticamente inclinados: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

El enfoque recomendado es utilizar factores de corrección (multiplicar por una potencia adecuada de 10 para que la aritmética ocurra entre enteros). Por ejemplo, en el caso de 0.1 * 0.2, el factor de corrección es 10, y está realizando el cálculo:

> var x = 0.1
> var y = 0.2
> var cf = 10
> x * y
0.020000000000000004
> (x * cf) * (y * cf) / (cf * cf)
0.02

Una solución (muy rápida) se parece a:

var _cf = (function() {
  function _shift(x) {
    var parts = x.toString().split('.');
    return (parts.length < 2) ? 1 : Math.pow(10, parts[1].length);
  }
  return function() { 
    return Array.prototype.reduce.call(arguments, function (prev, next) { return prev === undefined || next === undefined ? undefined : Math.max(prev, _shift (next)); }, -Infinity);
  };
})();

Math.a = function () {
  var f = _cf.apply(null, arguments); if(f === undefined) return undefined;
  function cb(x, y, i, o) { return x + f * y; }
  return Array.prototype.reduce.call(arguments, cb, 0) / f;
};

Math.s = function (l,r) { var f = _cf(l,r); return (l * f - r * f) / f; };

Math.m = function () {
  var f = _cf.apply(null, arguments);
  function cb(x, y, i, o) { return (x*f) * (y*f) / (f * f); }
  return Array.prototype.reduce.call(arguments, cb, 1);
};

Math.d = function (l,r) { var f = _cf(l,r); return (l * f) / (r * f); };

En este caso:

> Math.m(0.1, 0.2)
0.02

Definitivamente recomiendo usar una biblioteca probada como SinfulJS

SheetJS
fuente
1
Me encanta esta solución elegante pero parece no ser perfecta: jsfiddle.net/Dm6F5/1 Math.a (76.65, 38.45) devuelve 115.10000000000002
nicolallias
3
Math.m (10,2332226616) me está dando "-19627406800", que es un valor negativo ... Espero que haya un límite superior, ya que podría estar causando este problema. Sugerir
Shiva Komuravelly
1
Todo esto se ve muy bien, pero parece tener un error o dos en algún lugar.
MrYellow
55
Una solución muy rápida, dijo ... arreglo roto que nadie dijo nunca.
Cozzbie
2
No uses el código anterior. Absolutamente no es una 'solución rápida' si no funciona. Esta es una pregunta relacionada con las matemáticas, por lo que se requiere precisión.
Drenai
49

¿Solo estás realizando multiplicación? Si es así, puede utilizar a su favor un secreto claro sobre la aritmética decimal. Es decir que NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals. Es decir que si tenemos, 0.123 * 0.12entonces sabemos que habrá 5 lugares decimales porque 0.123tiene 3 lugares decimales y 0.12tiene dos. Por lo tanto, si JavaScript nos dio un número como 0.014760000002podemos redondear con seguridad al quinto decimal sin temor a perder precisión.

Nate Zaugg
fuente
66
... y cómo obtener la cantidad exacta de decimales.
línea-o
77
0.5 * 0.2 = 0.10; Todavía puede truncar en 2 decimales (o menos). Pero nunca habrá un número con significado matemático más allá de esta ley.
Nate Zaugg
3
¿Tienes una cita para esto? También tenga en cuenta que lo mismo no es cierto para la división.
Griffin
3
@NateZaugg no puedes truncar los decimales desbordados, tienes que redondear la cantidad, porque 2090.5 * 8.61 es 17999.205 pero en flotante es 17999.204999999998
Lostfields
3
@Lostfields - ¡Estás en lo correcto! He actualizado mi respuesta.
Nate Zaugg
29

Estas buscando un sprintf implementación para JavaScript, de modo que pueda escribir flotantes con pequeños errores (ya que están almacenados en formato binario) en el formato que espera.

Pruebe javascript-sprintf , lo llamaría así:

var yourString = sprintf("%.2f", yourNumber);

para imprimir su número como flotante con dos decimales.

También puede usar Number.toFixed () para fines de visualización, si prefiere no incluir más archivos simplemente para el redondeo de punto flotante a una precisión dada.

Douglas
fuente
44
Creo que esta es la solución más limpia. A menos que realmente necesite que el resultado sea 0.02, el pequeño error es insignificante. Parece que lo importante es que su número se muestre bien, no que tenga una precisión arbitraria.
Long Ouyang
2
Para mostrar esta es la mejor opción, para cálculos complicados, verifique la respuesta de Borgwardt.
No disponible el
44
Pero, de nuevo, esto devolverá exactamente la misma cadena que yourNumber.toFixed (2).
Robert
27

Estoy descubriendo que BigNumber.js satisface mis necesidades.

Una biblioteca de JavaScript para aritmética decimal y no decimal de precisión arbitraria.

Tiene buena documentación y el autor responde muy diligentemente a los comentarios.

El mismo autor tiene otras 2 bibliotecas similares:

Big.js

Una pequeña y rápida biblioteca de JavaScript para aritmética decimal de precisión arbitraria. La hermana pequeña de bignumber.js.

y Decimal.js

Un tipo decimal de precisión arbitraria para JavaScript.

Aquí hay un código usando BigNumber:

$(function(){

  
  var product = BigNumber(.1).times(.2);  
  $('#product').text(product);

  var sum = BigNumber(.1).plus(.2);  
  $('#sum').text(sum);


});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<!-- 1.4.1 is not the current version, but works for this example. -->
<script src="http://cdn.bootcss.com/bignumber.js/1.4.1/bignumber.min.js"></script>

.1 &times; .2 = <span id="product"></span><br>
.1 &plus; .2 = <span id="sum"></span><br>

Ronnie Overby
fuente
3
Usar una biblioteca es definitivamente la mejor opción en mi opinión.
Anthony
1
Desde este enlace github.com/MikeMcl/big.js/issues/45 bignumber.js -> financial decimal.js -> scientific big.js -> ???
Vee
20
var times = function (a, b) {
    return Math.round((a * b) * 100)/100;
};

---o---

var fpFix = function (n) {
    return Math.round(n * 100)/100;
};

fpFix(0.1*0.2); // -> 0.02

---además---

var fpArithmetic = function (op, x, y) {
    var n = {
            '*': x * y,
            '-': x - y,
            '+': x + y,
            '/': x / y
        }[op];        

    return Math.round(n * 100)/100;
};

--- como en ---

fpArithmetic('*', 0.1, 0.2);
// 0.02

fpArithmetic('+', 0.1, 0.2);
// 0.3

fpArithmetic('-', 0.1, 0.2);
// -0.1

fpArithmetic('/', 0.2, 0.1);
// 2
shawndumas
fuente
44
Creo que eso daría el mismo problema como resultado. Devuelve un punto flotante, por lo que es muy probable que el valor de retorno también sea "incorrecto".
Gertjan
1
Muy inteligente y útil, +1.
Jonatas Walker
18

Esta función determinará la precisión necesaria a partir de la multiplicación de dos números de coma flotante y devolverá un resultado con la precisión adecuada. Elegante aunque no lo es.

function multFloats(a,b){
  var atens = Math.pow(10,String(a).length - String(a).indexOf('.') - 1), 
      btens = Math.pow(10,String(b).length - String(b).indexOf('.') - 1); 
  return (a * atens) * (b * btens) / (atens * btens); 
}
Gabriel
fuente
Ew. Sí, convierta los números en cadenas para las matemáticas de coma flotante y también propongamos eso como respuesta.
Andrew
17

Sorprendentemente, esta función aún no se ha publicado, aunque otras tienen variaciones similares. Es de los documentos web de MDN para Math.round (). Es conciso y permite una precisión variable.

function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}

console.log (precisionRound (1234.5678, 1)); // salida esperada: 1234.6

console.log (precisionRound (1234.5678, -1)); // salida esperada: 1230

var inp = document.querySelectorAll('input');
var btn = document.querySelector('button');

btn.onclick = function(){
  inp[2].value = precisionRound( parseFloat(inp[0].value) * parseFloat(inp[1].value) , 5 );
};

//MDN function
function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}
button{
display: block;
}
<input type='text' value='0.1'>
<input type='text' value='0.2'>
<button>Get Product</button>
<input type='text'>

ACTUALIZACIÓN: 20 / Ago / 2019 Acabo de notar este error. Creo que se debe a un error de precisión de coma flotante con Math.round ().

precisionRound(1.005, 2) // produces 1, incorrect, should be 1.01

Estas condiciones funcionan correctamente:

precisionRound(0.005, 2) // produces 0.01
precisionRound(1.0005, 3) // produces 1.001
precisionRound(1234.5, 0) // produces 1235
precisionRound(1234.5, -1) // produces 1230

Reparar:

function precisionRoundMod(number, precision) {
  var factor = Math.pow(10, precision);
  var n = precision < 0 ? number : 0.01 / factor + number;
  return Math.round( n * factor) / factor;
}

Esto solo agrega un dígito a la derecha al redondear decimales. MDN ha actualizado la página Math.round para que alguien pueda proporcionar una mejor solución.

HelloWorldPeace
fuente
respuesta incorrecta. 10.2 siempre devolverá 10.19. jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas
@ Žilvinas El enlace JSBin que publicó no utiliza la función MDN que se menciona arriba. Creo que tu comentario está dirigido a la persona equivocada.
HelloWorldPeace
13

Solo tiene que decidir cuántos dígitos decimales realmente desea, no puede tener el pastel y comerlo también :-)

Los errores numéricos se acumulan con cada operación adicional y si no la corta temprano, solo crecerá. Las bibliotecas numéricas que presentan resultados que se ven limpios simplemente cortan los últimos 2 dígitos en cada paso, los coprocesadores numéricos también tienen una longitud "normal" y "completa" por la misma razón. Los cuf-offs son baratos para un procesador pero muy caros para usted en un script (multiplicando y dividiendo y usando pov (...)). Una buena práctica matemática le proporcionaría un piso (x, n) para hacer el corte por usted.

Entonces, como mínimo, debe hacer var / constante global con pov (10, n), lo que significa que decidió la precisión que necesita :-) Luego haga:

Math.floor(x*PREC_LIM)/PREC_LIM  // floor - you are cutting off, not rounding

También podría seguir haciendo cálculos matemáticos y solo cortar al final, suponiendo que solo muestre y no haga if-s con resultados. Si puede hacer eso, entonces .toFixed (...) podría ser más eficiente.

Si está haciendo if-s / comparaciones y no quiere cortar, entonces también necesita una pequeña constante, generalmente llamada eps, que es un decimal más que el error máximo esperado. Digamos que su punto de corte son los dos últimos decimales; entonces su eps tiene 1 en el 3er lugar desde el último (3er menos significativo) y puede usarlo para comparar si el resultado está dentro del rango de eps esperado (0.02 -eps <0.1 * 0.2 <0.02 + eps).

ZXX
fuente
También puede agregar 0.5 para hacer el redondeo de un hombre pobre: ​​Math.floor (x * PREC_LIM + 0.5) / PREC_LIM
cmroanirgo
Tenga en cuenta que, por ejemplo, Math.floor(-2.1)es -3. Así que quizás use, por ejemploMath[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
MikeM
¿Por qué en floorlugar de round?
Quinn Commandado
12

Puede usar parseFloat()y toFixed()si desea evitar este problema para una operación pequeña:

a = 0.1;
b = 0.2;

a + b = 0.30000000000000004;

c = parseFloat((a+b).toFixed(2));

c = 0.3;

a = 0.3;
b = 0.2;

a - b = 0.09999999999999998;

c = parseFloat((a-b).toFixed(2));

c = 0.1;
Softwareddy
fuente
11

La función round () en phpjs.org funciona bien: http://phpjs.org/functions/round

num = .01 + .06;  // yields 0.0699999999999
rnum = round(num,12); // yields 0.07
Tom
fuente
2
@jrg Por convención, los números que terminan con un "5" se redondean al par más cercano (porque siempre redondear hacia arriba o hacia abajo introduciría un sesgo en sus resultados). Por lo tanto, 4.725 redondeado a dos decimales debería ser de hecho 4.72.
Mark A. Durham
9

0.6 * 3 ¡es increíble!)) Para mí esto funciona bien:

function dec( num )
{
    var p = 100;
    return Math.round( num * p ) / p;
}

Muy muy simple))

Олег Всильдеревьев
fuente
¿Funcionaría esto con algo así 8.22e-8 * 1.3?
Paul Carlton
0.6 x 3 = 1.8, el código que da resultados a 2 ... así que no es bueno.
Zyo
@Zyo Devuelve 1.8 en esta instancia. ¿Cómo lo corriste?
Drenai
Interesante. Puede intercambiar los operadores de multiplicación y división en esto y también funciona.
Andrew
9

Tenga en cuenta que para el uso general, es probable que este comportamiento sea aceptable.
El problema surge cuando se comparan esos valores de coma flotante para determinar una acción apropiada.
Con el advenimiento de ES6, Number.EPSILONse define una nueva constante para determinar el margen de error aceptable:
Entonces, en lugar de realizar la comparación de esta manera

0.1 + 0.2 === 0.3 // which returns false

puede definir una función de comparación personalizada, como esta:

function epsEqu(x, y) {
    return Math.abs(x - y) < Number.EPSILON;
}
console.log(epsEqu(0.1+0.2, 0.3)); // true

Fuente: http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon

usuario10089632
fuente
En mi caso, Number.EPSILON era demasiado pequeño, lo que resultó en, por ejemplo,0.9 !== 0.8999999761581421
Tom
8

El resultado obtenido es correcto y bastante consistente en las implementaciones de coma flotante en diferentes lenguajes, procesadores y sistemas operativos: lo único que cambia es el nivel de inexactitud cuando el flotante es en realidad un doble (o superior).

0.1 en puntos flotantes binarios es como 1/3 en decimal (es decir, 0.3333333333333 ... para siempre), simplemente no hay una forma precisa de manejarlo.

Si se trata de flotadores, siempre espere pequeños errores de redondeo, por lo que también tendrá que redondear el resultado mostrado a algo sensato. A cambio, obtienes aritmética muy, muy rápida y poderosa porque todos los cálculos están en el binario nativo del procesador.

La mayoría de las veces la solución no es cambiar a aritmética de punto fijo, principalmente porque es mucho más lenta y el 99% del tiempo simplemente no necesita la precisión. Si se trata de cosas que necesitan ese nivel de precisión (por ejemplo, transacciones financieras), Javascript probablemente no sea la mejor herramienta para usar de todos modos (ya que desea aplicar los tipos de punto fijo, un lenguaje estático probablemente sea mejor) )

Estás buscando la solución elegante, entonces me temo que es esta: los flotadores son rápidos pero tienen pequeños errores de redondeo, siempre se redondea a algo sensato cuando se muestran sus resultados.

Keith
fuente
8

Para evitar esto, debe trabajar con valores enteros en lugar de puntos flotantes. Entonces, cuando desee tener una precisión de 2 posiciones, trabaje con los valores * 100, para 3 posiciones use 1000. Al mostrar, use un formateador para colocar en el separador.

Muchos sistemas omiten trabajar con decimales de esta manera. Esa es la razón por la cual muchos sistemas funcionan con centavos (como número entero) en lugar de dólares / euros (como punto flotante).

Gertjan
fuente
7

Problema

El punto flotante no puede almacenar todos los valores decimales exactamente. Por lo tanto, cuando se utilizan formatos de punto flotante, siempre habrá errores de redondeo en los valores de entrada. Los errores en las entradas, por supuesto, resultan en errores en la salida. En el caso de una función u operador discreto, puede haber grandes diferencias en la salida alrededor del punto donde la función u operador es discreto.

Entrada y salida para valores de coma flotante

Por lo tanto, cuando use variables de punto flotante, siempre debe ser consciente de esto. Y cualquier salida que desee de un cálculo con puntos flotantes siempre debe formatearse / condicionarse antes de mostrarse con esto en mente.
Cuando solo se utilizan operadores y funciones continuas, a menudo se redondea a la precisión deseada (no se trunca). Las características de formato estándar que se utilizan para convertir flotantes en cadenas generalmente lo harán por usted.
Debido a que el redondeo agrega un error que puede causar que el error total sea más de la mitad de la precisión deseada, la salida debe corregirse según la precisión esperada de las entradas y la precisión deseada de la salida. Debieras

  • Redondee las entradas a la precisión esperada o asegúrese de que no se puedan ingresar valores con mayor precisión.
  • Agregue un valor pequeño a las salidas antes de redondearlas / formatearlas, que sea menor o igual a 1/4 de la precisión deseada y mayor que el error máximo esperado causado por los errores de redondeo en la entrada y durante el cálculo. Si eso no es posible, la combinación de la precisión del tipo de datos utilizado no es suficiente para proporcionar la precisión de salida deseada para su cálculo.

Estas 2 cosas generalmente no se hacen y, en la mayoría de los casos, las diferencias causadas por no hacerlo son demasiado pequeñas para ser importantes para la mayoría de los usuarios, pero ya tenía un proyecto en el que los usuarios no aceptaban los resultados sin esas correcciones.

Funciones u operadores discretos (como módulo)

Cuando intervienen operadores o funciones discretas, es posible que se requieran correcciones adicionales para asegurarse de que el resultado sea el esperado. Redondear y agregar pequeñas correcciones antes de redondear no puede resolver el problema.
Es posible que se requiera una verificación / corrección especial en los resultados de cálculo intermedios, inmediatamente después de aplicar la función u operador discreto. Para un caso específico (operador de módulo), vea mi respuesta a la pregunta: ¿Por qué el operador de módulo devuelve un número fraccionario en javascript?

Mejor evitar tener el problema

A menudo es más eficiente evitar estos problemas mediante el uso de tipos de datos (formatos enteros o de punto fijo) para cálculos como este, que pueden almacenar la entrada esperada sin errores de redondeo. Un ejemplo de esto es que nunca debe usar valores de punto flotante para cálculos financieros.

Stefan Mondelaers
fuente
4

Echa un vistazo a la aritmética de punto fijo . Probablemente resolverá su problema, si el rango de números en el que desea operar es pequeño (p. Ej., Moneda). Lo redondearía a unos pocos valores decimales, que es la solución más simple.

Mario
fuente
55
El problema no es punto flotante vs. punto fijo, el problema es binario vs. decimal.
Michael Borgwardt
4

Prueba mi biblioteca de aritmética chilena, que puedes ver aquí . Si quieres una versión posterior, puedo conseguirte una.

Robert L
fuente
4

No puede representar la mayoría de las fracciones decimales exactamente con los tipos de punto flotante binario (que es lo que ECMAScript usa para representar valores de punto flotante). Por lo tanto, no existe una solución elegante a menos que utilice tipos aritméticos de precisión arbitraria o un tipo de coma flotante basado en decimales. Por ejemplo, la aplicación Calculadora que se incluye con Windows ahora usa aritmética de precisión arbitraria para resolver este problema .

MSN
fuente
4

ingrese la descripción de la imagen aquí

    You can use library https://github.com/MikeMcl/decimal.js/. 
    it will   help  lot to give proper solution. 
    javascript console output 95 *722228.630 /100 = 686117.1984999999
    decimal library implementation 
    var firstNumber = new Decimal(95);
    var secondNumber = new Decimal(722228.630);
    var thirdNumber = new Decimal(100);
    var partialOutput = firstNumber.times(secondNumber);
    console.log(partialOutput);
    var output = new Decimal(partialOutput).div(thirdNumber);
    alert(output.valueOf());
    console.log(output.valueOf())== 686117.1985
Ashish Singhal
fuente
3

Tiene razón, la razón de esto es la precisión limitada de los números de coma flotante. Almacene sus números racionales como una división de dos números enteros y en la mayoría de las situaciones podrá almacenar números sin pérdida de precisión. Cuando se trata de imprimir, es posible que desee mostrar el resultado como fracción. Con la representación que propuse, se vuelve trivial.

Por supuesto, eso no ayudará mucho con los números irracionales. Pero es posible que desee optimizar sus cálculos de la forma en que causarán el menor problema (por ejemplo, detectar situaciones como sqrt(3)^2).

skalee
fuente
Tiene razón, la razón de esto es la precisión limitada de los números de coma flotante ; en <pedant>realidad, el OP lo </pedant>
atribuyó a
3

Tuve un desagradable problema de error de redondeo con el mod 3. A veces, cuando debería obtener 0, obtenía .000 ... 01. Eso es bastante fácil de manejar, solo pruebe <= .01. Pero a veces obtenía 2.99999999999998. ¡AY!

BigNumbers resolvió el problema, pero introdujo otro problema, algo irónico. Al intentar cargar 8.5 en BigNumbers, me informaron que en realidad era 8.4999 ... y tenía más de 15 dígitos significativos. Esto significaba que BigNumbers no podía aceptarlo (creo que mencioné que este problema era algo irónico).

Solución simple al problema irónico:

x = Math.round(x*100);
// I only need 2 decimal places, if i needed 3 I would use 1,000, etc.
x = x / 100;
xB = new BigNumber(x);
mcgeo52
fuente
2

Número de uso (1.234443) .toFixed (2); imprimirá 1.23

function test(){
    var x = 0.1 * 0.2;
    document.write(Number(x).toFixed(2));
}
test();
Harish.bazee
fuente
2

decimal.js , big.js o bignumber.js se pueden usar para evitar problemas de manipulación de punto flotante en Javascript:

0.1 * 0.2                                // 0.020000000000000004
x = new Decimal(0.1)
y = x.times(0.2)                          // '0.2'
x.times(0.2).equals(0.2)                  // true

big.js: minimalista; fácil de usar; precisión especificada en decimales; precisión aplicada solo a la división.

bignumber.js: bases 2-64; opciones de configuración; Yaya; Infinito; precisión especificada en decimales; precisión aplicada solo a la división; prefijos base

decimal.js: bases 2-64; opciones de configuración; Yaya; Infinito; potencias no enteras, exp, ln, log; precisión especificada en dígitos significativos; precisión siempre aplicada; números al azar.

enlace a comparaciones detalladas

Erikas Pliauksta
fuente
2

Elegante, predecible y reutilizable

Abordemos el problema de una manera elegante y reutilizable. Las siguientes siete líneas le permitirán acceder a la precisión de coma flotante que desee en cualquier número simplemente agregando .decimalal final del número, fórmula o Mathfunción incorporada.

// First extend the native Number object to handle precision. This populates
// the functionality to all math operations.

Object.defineProperty(Number.prototype, "decimal", {
  get: function decimal() {
    Number.precision = "precision" in Number ? Number.precision : 3;
    var f = Math.pow(10, Number.precision);
    return Math.round( this * f ) / f;
  }
});


// Now lets see how it works by adjusting our global precision level and 
// checking our results.

console.log("'1/3 + 1/3 + 1/3 = 1' Right?");
console.log((0.3333 + 0.3333 + 0.3333).decimal == 1); // true

console.log(0.3333.decimal); // 0.333 - A raw 4 digit decimal, trimmed to 3...

Number.precision = 3;
console.log("Precision: 3");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0.001

Number.precision = 2;
console.log("Precision: 2");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 1;
console.log("Precision: 1");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 0;
console.log("Precision: 0");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

¡Salud!

Bernesto
fuente
2
Si elige hacer un voto negativo, al menos proporcione una razón.
Bernesto
1

Utilizar

var x = 0.1*0.2;
 x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);
Himadri
fuente
44
Hmm ... pero ten en cuenta que esto siempre se redondea a 2 decimales. Eso, por supuesto, sería una opción, pero ¿qué pasa con el cálculo 0.55 * 0.55 (ya que no sé los números exactos de antemano. Eso daría 0.3 en lugar de 0.3025. Por supuesto que podría usar Math.round(x*Math.pow(10,4))/Math.pow(10,4);. El redondeo siempre es una opción, pero yo sólo quería saber si hay alguna solución mejor
Juri
10.2 siempre devuelve 10.19 jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas
1

no es elegante pero hace el trabajo (elimina los ceros finales)

var num = 0.1*0.2;
alert(parseFloat(num.toFixed(10))); // shows 0.02
Peter
fuente
66
toFixed no siempre funciona: stackoverflow.com/questions/661562/…
Sam Hasler
1

Esto funciona para mi:

function round_up( value, precision ) { 
    var pow = Math.pow ( 10, precision ); 
    return ( Math.ceil ( pow * value ) + Math.ceil ( pow * value - Math.ceil ( pow * value ) ) ) / pow; 
}

round_up(341.536, 2); // 341.54
Antonio Max
fuente
1
desafortunadamente, round_up (4.15,2) => 4.16.
jrg
1

Salida utilizando la siguiente función:

var toFixedCurrency = function(num){
    var num = (num).toString();
    var one = new RegExp(/\.\d{1}$/).test(num);
    var two = new RegExp(/\.\d{2,}/).test(num);
    var result = null;

    if(one){ result = num.replace(/\.(\d{1})$/, '.$10');
    } else if(two){ result = num.replace(/\.(\d{2})\d*/, '.$1');
    } else { result = num*100; }

    return result;
}

function test(){
    var x = 0.1 * 0.2;
    document.write(toFixedCurrency(x));
}

test();

Presta atención a la salida toFixedCurrency(x).

Julio Paulillo
fuente