Como lidar com a precisão do número de ponto flutuante em JavaScript?

Eu tenho o seguinte script de teste fictício:

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

Isto irá imprimir o resultado 0.020000000000000004 enquanto deve imprimir apenas 0.02 (se você usar sua calculadora). Tanto quanto eu entendi isso é devido a erros na precisão de multiplicação de ponto flutuante.

Alguém tem uma boa solução para que, nesse caso, eu consiga o resultado correto de 0.02 ? Eu sei que existem funções como toFixed ou arredondar seria outra possibilidade, mas eu gostaria de realmente ter o número inteiro impresso sem qualquer corte e arredondamento. Só queria saber se algum de vocês tem uma solução legal e elegante.

Claro, senão vou arredondar para cerca de 10 dígitos ou mais.

Do guia de ponto flutuante :

O que posso fazer para evitar esse problema?

Isso depende do tipo de cálculo que você está fazendo.

  • Se você realmente precisa dos seus resultados para sumr exatamente, especialmente quando você trabalha com dinheiro: use um tipo de dados decimal especial.
  • Se você simplesmente não quiser ver todas as casas decimais extras: simplesmente formate seu resultado arredondado para um número fixo de casas decimais ao exibi-lo.
  • Se você não tiver nenhum tipo de dado decimal disponível, uma alternativa é trabalhar com inteiros, por exemplo, fazer cálculos de dinheiro inteiramente em centavos. Mas isso é mais trabalho e tem algumas desvantagens.

Observe que o primeiro ponto só se aplica se você realmente precisar de um comportamento decimal preciso específico. A maioria das pessoas não precisa disso, apenas ficam irritadas porque seus programas não funcionam corretamente com números como 1/10 sem perceber que nem piscariam com o mesmo erro se ocorresse com 1/3.

Se o primeiro ponto realmente se aplica a você, use BigDecimal para JavaScript , que não é nada elegante, mas na verdade resolve o problema em vez de fornecer uma solução imperfeita.

Eu gosto da solução de Pedro Ladaria e uso algo similar.

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

Diferentemente da solução de Pedros, isso arredondará 0.999 … repetindo e é preciso para mais / menos um no dígito menos significativo.

Nota: Ao lidar com floats de 32 ou 64 bits, você deve usar toPrecision (7) e toPrecision (15) para obter melhores resultados. Veja esta pergunta para informações sobre o porquê.

Para o matematicamente inclinado: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

A abordagem recomendada é usar fatores de correção (multiplicar por uma potência adequada de 10 para que a aritmética ocorra entre números inteiros). Por exemplo, no caso de 0.1 * 0.2 , o fator de correção é 10 e você está executando o 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 

Uma solução (muito rápida) se parece com algo 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); }; 

Nesse caso:

 > Math.m(0.1, 0.2) 0.02 

Eu definitivamente recomendo usar uma biblioteca testada como o SinfulJS

Você está apenas realizando multiplicação? Se sim, então você pode usar para sua vantagem um segredo sobre a aritmética decimal. Isso é que NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals . Ou seja, se temos 0.123 * 0.12 sabemos que haverá 5 casas decimais porque 0.123 tem 3 casas decimais e 0.12 tem duas casas decimais. Assim, se o JavaScript nos fornecer um número como 0.014760000002 , poderemos arredondar com segurança até a quinta casa decimal sem medo de perder a precisão.

Você está procurando uma implementação do sprintf para JavaScript, para que você possa gravar floats com pequenos erros neles (já que eles são armazenados em formato binário) em um formato que você espera.

Tente javascript-sprintf , você chamaria assim:

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

para imprimir o seu número como um float com duas casas decimais.

Você também pode usar Number.toFixed () para fins de exibição, se preferir não include mais arquivos apenas para arredondamento de ponto flutuante para uma determinada precisão.

 var times = function (a, b) { return Math.round((a * b) * 100)/100; }; 

—ou—

 var fpFix = function (n) { return Math.round(n * 100)/100; }; fpFix(0.1*0.2); // -> 0.02 

—Além disso—

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

— como em —

 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 

Essa function determinará a precisão necessária a partir da multiplicação de dois números de ponto flutuante e retornará um resultado com a precisão apropriada. Elegante embora não seja.

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

Eu estou achando que o BigNumber.js atende às minhas necessidades.

Uma biblioteca JavaScript para aritmética decimal e não decimal com precisão arbitrária.

Tem boa documentação e o autor é muito diligente em responder ao feedback.

O mesmo autor tem 2 outras bibliotecas semelhantes:

Big.js

Uma pequena biblioteca JavaScript rápida para aritmética decimal de precisão arbitrária. A irmãzinha de bignumber.js.

e Decimal.js

Um tipo decimal de precisão arbitrária para JavaScript.

Aqui está algum código usando BigNumber:

 $(function(){ var product = BigNumber(.1).times(.2); $('#product').text(product); var sum = BigNumber(.1).plus(.2); $('#sum').text(sum); }); 
    .1 × .2 = 
.1 + .2 =

Você apenas tem que se decidir sobre quantos dígitos decimais você realmente quer – não pode ter o bolo e comê-lo também 🙂

Erros numéricos se acumulam a cada nova operação e, se você não cortá-los cedo, vai crescer. Bibliotecas numéricas que apresentam resultados que parecem limpos simplesmente cortam os últimos 2 dígitos a cada passo, coprocessadores numéricos também têm um comprimento “normal” e “completo” pelo mesmo motivo. Cuf-offs são baratos para um processador, mas muito caros para você em um script (multiplicando e dividindo e usando o pov (…)). Uma boa lib de matemática forneceria floor (x, n) para fazer o corte para você.

Então, pelo menos você deveria fazer global var / constant com pov (10, n) – significando que você decidiu a precisão que você precisa 🙂 Então faça:

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

Você também pode continuar fazendo matemática e apenas cortar no final – assumindo que você está exibindo apenas e não está fazendo com resultados. Se você puder fazer isso, então .toFixed (…) pode ser mais eficiente.

Se você está fazendo if-s / comparações e não quer cortar de então você também precisa de uma pequena constante, geralmente chamada de eps, que é uma casa decimal maior que o máximo de erro esperado. Digamos que o seu cut-off tenha os dois últimos decimais – então o eps tem 1 no 3º lugar do último (3º menos significativo) e você pode usá-lo para comparar se o resultado está dentro do intervalo EPS esperado (0.02 -eps <0.1 * 0,2 <0,02 + eps).

A function round () em phpjs.org funciona bem: http://phpjs.org/functions/round

 num = .01 + .06; // yields 0.0699999999999 rnum = round(num,12); // yields 0.07 

O resultado obtido é correto e bastante consistente em implementações de ponto flutuante em diferentes linguagens, processadores e sistemas operacionais – a única coisa que muda é o nível de imprecisão quando a flutuação é realmente dupla (ou superior).

0.1 em pontos flutuantes binários é como 1/3 no decimal (ou seja, 0.3333333333333 … para sempre), simplesmente não há maneira precisa de lidar com isso.

Se você está lidando com carros alegóricos sempre espera pequenos erros de arredondamento, então você também sempre terá que arredondar o resultado exibido para algo sensato. Em troca, você obtém uma aritmética muito muito rápida e poderosa, porque todas as computações estão no binário nativo do processador.

Na maioria das vezes a solução não é mudar para a aritmética de ponto fixo, principalmente porque é muito mais lenta e 99% do tempo você não precisa da precisão. Se você está lidando com coisas que precisam desse nível de precisão (por exemplo, transactions financeiras) Javascript provavelmente não é a melhor ferramenta para usar de qualquer maneira (como você quer impor os tipos de ponto fixo, uma linguagem estática é provavelmente melhor ).

Você está procurando a solução elegante, então eu temo que seja isso: os floats são rápidos, mas têm pequenos erros de arredondamento – sempre ronda a algo sensato ao exibir seus resultados.

Para evitar isso, você deve trabalhar com valores inteiros em vez de pontos flutuantes. Então, quando você quiser ter 2 posições de precisão trabalhar com os valores * 100, para 3 posições use 1000. Ao exibir você usa um formatador para colocar no separador.

Muitos sistemas omitem trabalhar com decimais dessa maneira. Essa é a razão pela qual muitos sistemas trabalham com centavos (como inteiro) em vez de dólares / euros (como ponto flutuante).

Você pode usar parseFloat() e toFixed() se quiser ignorar esse problema para uma pequena operação:

 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((ab).toFixed(2)); c = 0.1; 

0,6 * 3 é incrível!)) Para mim, isso funciona bem:

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

Muito muito simples))

Dê uma olhada na aritmética de ponto fixo . Ele provavelmente resolverá seu problema, se o intervalo de números em que você deseja operar for pequeno (por exemplo, moeda). Eu arredondaria para alguns valores decimais, que é a solução mais simples.

Experimente minha biblioteca de aritmética chiliadic, que você pode ver aqui . Se você quiser uma versão posterior, eu posso te dar uma.

Você não pode representar a maioria das frações decimais exatamente com tipos binários de ponto flutuante (que é o que o ECMAScript usa para representar valores de ponto flutuante). Portanto, não há uma solução elegante a menos que você use tipos aritméticos de precisão arbitrária ou um tipo de ponto flutuante baseado em decimal. Por exemplo, o aplicativo Calculadora que acompanha o Windows agora usa aritmética de precisão arbitrária para resolver esse problema .

Problema

O ponto flutuante não pode armazenar todos os valores decimais exatamente. Portanto, ao usar formatos de ponto flutuante, sempre haverá erros de arredondamento nos valores de input. Os erros nas inputs do curso resultam em erros na saída. No caso de uma function ou operador discreto, pode haver grandes diferenças na saída em torno do ponto em que a function ou o operador é discreto.

Entrada e saída para valores de ponto flutuante

Portanto, ao usar variables ​​de ponto flutuante, você deve sempre estar ciente disso. E qualquer saída que você queira de um cálculo com pontos flutuantes deve sempre ser formatada / condicionada antes de ser exibida com isso em mente.
Quando apenas funções contínuas e operadores são usados, arredondamentos com a precisão desejada geralmente são feitos (não truncam). Os resources de formatação padrão usados ​​para converter floats em string geralmente fazem isso para você.
Como o arredondamento adiciona um erro que pode fazer com que o erro total seja mais da metade da precisão desejada, a saída deve ser corrigida com base na precisão esperada das inputs e na precisão desejada da saída. Você deve

  • Arredonde as inputs para a precisão esperada ou certifique-se de que nenhum valor possa ser inserido com maior precisão.
  • Adicione um valor pequeno às saídas antes de arredondá-las / formatá-las, que seja menor ou igual a 1/4 da precisão desejada e maior que o erro máximo esperado causado por erros de arredondamento na input e durante o cálculo. Se isso não for possível, a combinação da precisão do tipo de dados usado não é suficiente para fornecer a precisão de saída desejada para o seu cálculo.

Essas duas coisas geralmente não são feitas e, na maioria dos casos, as diferenças causadas por não fazê-las são muito pequenas para serem importantes para a maioria dos usuários, mas eu já tinha um projeto em que a saída não era aceita pelos usuários sem essas correções.

Funções discretas ou operadores (como modula)

Quando operadores ou funções distintas estão envolvidos, correções extras podem ser necessárias para garantir que a saída seja a esperada. Arredondar e adicionar pequenas correções antes do arredondamento não pode resolver o problema.
Uma verificação / correção especial nos resultados do cálculo intermediário, imediatamente após a aplicação da function discreta ou do operador, pode ser necessária. Para um caso específico (operador modula), veja minha resposta na pergunta: Por que o operador de módulo retorna o número fracionário em javascript?

Melhor evitar ter o problema

Geralmente, é mais eficiente evitar esses problemas usando tipos de dados (formatos de ponto inteiro ou fixo) para cálculos como este, que podem armazenar a input esperada sem erros de arredondamento. Um exemplo disso é que você nunca deve usar valores de ponto flutuante para cálculos financeiros.

Surpreendentemente, esta function ainda não foi publicada, embora outras tenham variações semelhantes. É dos documentos da Web do MDN para Math.round (). É conciso e permite precisão variável.

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

console.log (precisionRound (1234,5678, 1)); // saída esperada: 1234,6

console.log (precisionRound (1234,5678, -1)); // produção 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; } 
     

Usar

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

Você está certo, a razão para isso é a precisão limitada de números de ponto flutuante. Armazene seus números racionais como uma divisão de dois números inteiros e, na maioria das situações, você poderá armazenar números sem qualquer perda de precisão. Quando se trata de impressão, você pode querer exibir o resultado como fração. Com a representação que propus, isso se torna trivial.

Claro que isso não ajudará muito com números irracionais. Mas você pode querer otimizar seus cálculos na maneira como eles causarão o menor problema (por exemplo, detectando situações como sqrt(3)^2) .

Use Number (1.234443) .toFixed (2); vai imprimir 1.23

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

Eu tinha um problema de erro de arredondamento desagradável com o mod 3. Às vezes, quando eu deveria receber 0, eu obtinha 0,000 … 01. Isso é fácil de manusear, apenas teste para < = 0,01. Mas às vezes eu pegava 2,999999999999998. OUCH!

BigNumbers resolveu o problema, mas introduziu outro problema, um pouco irônico. Ao tentar carregar 8.5 no BigNumbers, fui informado de que realmente era 8.4999… e tinha mais de 15 dígitos significativos. Isso significa que BigNumbers não poderia aceitá-lo (acredito que mencionei que esse problema era um pouco irônico).

Solução simples para o 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); 

insira a descrição da imagem aqui

  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 

Observe que, para o uso geral, esse comportamento provavelmente será aceitável.
O problema surge ao comparar esses valores de pontos flutuantes para determinar uma ação apropriada.
Com o advento do ES6, uma nova constante Number.EPSILON é definida para determinar a margem de erro aceitável:
Então, ao invés de realizar a comparação como esta

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

você pode definir uma function de comparação personalizada, assim:

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

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

não elegante, mas faz o trabalho (remove zeros à direita)

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

Isso funciona para mim:

 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 

Saída usando a seguinte function:

 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 atenção na saída toFixedCurrency(x) .

ao adicionar dois valores de flutuação, ele nunca fornece os valores precisos, portanto precisamos fixá-lo em um número que nos ajude a comparar.

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

Eu não sou muito bom em programação, mas estava realmente interessado neste tópico, então eu tentei entender como resolver isso sem usar nenhuma biblioteca ou script

Eu escrevi isso no scratchpad

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

você pode precisar refatorar este código, porque eu não sou bom em programar 🙂