number float decimals decimales javascript floating-point

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



number javascript (30)

Tengo el siguiente script de prueba ficticia:

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

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

¿Alguien tiene una buena solución para que en tal caso obtenga el resultado correcto 0.02 ? Sé que hay funciones como toFixed o redondear sería otra posibilidad, pero me gustaría que realmente se imprimiera el número completo sin cortes ni redondeos. Solo quería saber si alguno de ustedes tiene alguna solución agradable y elegante.

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


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


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

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

Muy, muy simple)


Desde 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 lugares decimales adicionales: simplemente formatee su resultado redondeado a un número fijo de lugares decimales cuando lo muestre.
  • Si no tiene un tipo de datos decimal disponible, 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 específico y preciso. La mayoría de las personas no lo necesitan, simplemente se irritan porque sus programas no funcionan correctamente con números como 1/10 sin darse cuenta de que ni siquiera parpadearían con 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.


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


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

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

Si está tratando con flotadores, siempre espere pequeños errores de redondeo, por lo que también siempre tendrá que redondear el resultado mostrado a algo razonable. A cambio, obtienes una 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 la aritmética de punto fijo, principalmente porque es mucho más lento y el 99% del tiempo simplemente no necesita la precisión. Si está lidiando con cosas que sí necesitan ese nivel de precisión (por ejemplo, transacciones financieras), probablemente JavaScript no sea la mejor herramienta para usar de todos modos (ya que desea aplicar los tipos de puntos fijos, es probable que un lenguaje estático sea mejor) ).

Está buscando la solución elegante, entonces me temo que esto es así: las flotaciones son rápidas pero tienen pequeños errores de redondeo, siempre redondeando algo sensato al mostrar sus resultados.


Está buscando una implementación de sprintf para JavaScript, por lo que puede escribir flotantes con pequeños errores (ya que están almacenados en formato binario) en el formato que espera.

Prueba javascript-sprintf , lo llamarías así:

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

para imprimir su número como un flotador 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.


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

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


Esto funciona para mí:

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


Estoy encontrando BigNumber.js satisface mis necesidades.

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

Tiene buena documentation y el autor es muy diligente en responder a los comentarios.

El mismo autor tiene otras 2 bibliotecas similares:

Big.js

Una biblioteca pequeña y rápida 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 algo de 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>


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


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 Pedros, esto redondeará 0.999 ... se repite y tiene una precisión de más / menos uno en el dígito menos significativo.

Nota: Cuando se trata de flotadores 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é.



No soy muy bueno programando, pero estaba realmente interesado en este tema, así que traté de entender cómo resolverlo sin usar bibliotecas ni scripts.

Escribí esto en el bloc de notas

var toAlgebraic = function(f1, f2) { let f1_base = Math.pow(10, f1.split(''.'')[1].length); let f2_base = Math.pow(10, f2.split(''.'')[1].length); f1 = parseInt(f1.replace(''.'', '''')); f2 = parseInt(f2.replace(''.'', '''')); let dif, base; if (f1_base > f2_base) { dif = f1_base / f2_base; base = f1_base; f2 = f2 * dif; } else { dif = f2_base / f1_base; base = f2_base; f1 = f1 * dif; } return (f1 * f2) / base; }; console.log(0.1 * 0.2); console.log(toAlgebraic("0.1", "0.2"));

Es posible que necesite refactorizar este código, porque no soy bueno en la programación :)


Para evitar esto, debe trabajar con valores enteros en lugar de puntos flotantes. Entonces, cuando quiera tener 2 posiciones, el trabajo de precisión con los valores * 100, para 3 posiciones, use 1000. Cuando se muestra, se usa un formateador para colocar el separador.

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


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

El enfoque recomendado es usar 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 ve algo así como:

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


Prueba mi biblioteca aritmética chiliadica, que puedes ver here . Si quieres una versión posterior, puedo conseguirte una.


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

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;


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

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


Si bien la adición de dos valores flotantes nunca da los valores precisos, debemos corregirlo a un número determinado que nos ayudará a comparar.

console.log ((parseFloat (0.1) + parseFloat (0.2)) toFixed (1) == parseFloat (0.3) .toFixed (1));

Solo tienes que decidir cuántos dígitos decimales quieres, no puedes tener el pastel y comértelo también :-)

Los errores numéricos se acumulan con cada operación adicional y, si no se corta antes, solo va a crecer. Las bibliotecas numéricas que presentan resultados 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-off son baratos para un procesador pero muy costosos para usted en un script (multiplicar y dividir y usar pov (...)). Una buena libreta de matemáticas proporcionaría piso (x, n) para hacer el corte por ti.

Por lo menos, debe hacer una 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 recortar al final, suponiendo que solo se muestra y no está haciendo if-s con resultados. Si puedes hacer eso, entonces .toFixed (...) podría ser más eficiente.

Si está haciendo comparaciones de if-s y no quiere cortar, entonces también necesita una pequeña constante, generalmente llamada eps, que es un lugar decimal más alto que el error máximo esperado. Diga que su valor de corte es los dos últimos decimales; luego, su eps tiene 1 en el tercer lugar del último (el tercero 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).


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


Tenga en cuenta que para el uso general, este comportamiento es probable que sea aceptable.
El problema surge al comparar los valores de los puntos flotantes para determinar una acción apropiada.
Con la llegada de ES6, se define una nueva constante Number.EPSILON para determinar el margen de error aceptable:
Así que en lugar de realizar la comparación como esta

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

Puedes 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


Tiene razón, la razón de ello es la precisión limitada de los números de punto 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 ninguna 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 que no ayudará mucho con números irracionales. Pero es posible que desee optimizar sus cálculos de la manera en que causarán el menor problema (por ejemplo, detectar situaciones como sqrt(3)^2) .


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

BigNumbers resolvió el problema, pero introdujo otro problema un tanto irónico. Al tratar de cargar 8.5 en BigNumbers, me informaron que en realidad eran 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 un tanto 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);


Use Number (1.234443) .toFixed (2); se imprimirá 1.23

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


Utilizar

var x = 0.1*0.2; x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);


no es elegante pero cumple su función (elimina los ceros finales)

var num = 0.1*0.2; alert(parseFloat(num.toFixed(10))); // shows 0.02


Problema

El punto flotante no puede almacenar exactamente todos los valores decimales. Entonces, cuando se usan formatos de punto flotante, siempre habrá errores de redondeo en los valores de entrada. Los errores en las entradas de los resultados del curso 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 o el operador son discretos.

Entrada y salida para valores de punto flotante.

Por lo tanto, cuando utilice variables de punto flotante, siempre debe tener esto en cuenta. Y cualquier salida que desee de un cálculo con puntos flotantes siempre debe formatearse / acondicionarse antes de mostrarse con esto en mente.
Cuando solo se usan funciones continuas y operadores, el redondeo a la precisión deseada a menudo lo hará (no truncar). 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 en función de 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, lo que es más pequeño o igual a 1/4 de la precisión deseada y más grande 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 hacerlas 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 el producto sin esas correcciones.

Funciones u operadores discretos (como modula)

Cuando intervienen operadores o funciones discretas, es posible que se requieran correcciones adicionales para asegurarse de que la salida sea la esperada. 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 del cálculo intermedio, inmediatamente después de aplicar la función discreta o el operador. Para un caso específico (operador de módulo), vea mi respuesta en la pregunta: ¿Por qué el operador de módulo devuelve el número fraccional en javascript?

Mejor evitar tener el problema.

A menudo es más eficiente evitar estos problemas utilizando tipos de datos (formatos de punto fijo o entero) 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.


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


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