Eu tenho o seguinte script de teste fictício:
function test() {
var x = 0.1 * 0.2;
document.write(x);
}
test();
Isso imprimirá o resultado 0.020000000000000004
enquanto deve ser impresso 0.02
(se você usar sua calculadora). Tanto quanto eu entendi isso é devido a erros na precisão da multiplicação de ponto flutuante.
Alguém tem uma boa solução para que, nesse caso, eu obtenha o resultado correto 0.02
? Eu sei que existem funções como toFixed
ou arredondamento, seria outra possibilidade, mas eu realmente gostaria que o número inteiro fosse impresso sem nenhum corte e arredondamento. Só queria saber se um de vocês tem alguma solução agradável e elegante.
Claro, caso contrário, arredondarei para cerca de 10 dígitos.
0.1
para um número de ponto flutuante binário finito.Respostas:
No Guia de ponto flutuante :
Observe que o primeiro ponto se aplica apenas se você realmente precisar de casas decimais precisas e específicas comportamento . A maioria das pessoas não precisa disso, apenas fica irritada que seus programas não funcionem corretamente com números como 1/10 sem perceber que nem sequer piscarão com o mesmo erro se isso ocorrer com 1/3.
Se o primeiro ponto realmente se aplicar a você, use o BigDecimal para JavaScript , que não é nada elegante, mas resolva o problema em vez de fornecer uma solução imperfeita.
fonte
console.log(9332654729891549)
realmente imprime9332654729891548
(ou seja, fora por um!);P
... Entre2⁵²
=4,503,599,627,370,496
e2⁵³
=9,007,199,254,740,992
os números representáveis são exatamente os números inteiros . Para a próxima faixa, desde2⁵³
que2⁵⁴
, tudo é multiplicado por2
, por isso os números representáveis são os mesmo aqueles , etc. Por outro lado, para a faixa anterior de2⁵¹
que2⁵²
, o espaçamento é0.5
, etc. Isto é devido ao simples aumento | diminuindo a base | expoente binário radix 2 | em / do valor flutuante de 64 bits (que por sua vez explica o comportamento 'inesperado' raramente documentado detoPrecision()
para valores entre0
e1
).Gosto da solução de Pedro Ladaria e uso algo semelhante.
Diferentemente da solução Pedros, isso arredondará 0,999 ... repetindo e é preciso mais / menos um no dígito menos significativo.
Nota: Ao lidar com flutuações 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ê.
fonte
toPrecision
retorna uma string em vez de um número. Isso nem sempre pode ser desejável.(9.99*5).toPrecision(2)
= 50 em vez de 49.95 porque toPrecision conta o número inteiro, não apenas decimais. Você pode usá-lotoPrecision(4)
, mas se o resultado for> 100, você estará sem sorte novamente, porque permitirá os três primeiros números e uma casa decimal, dessa forma mudando o ponto e tornando isso mais ou menos inutilizável. Acabei usando emtoFixed(2)
vez dissoPara os inclinados matematicamente: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
A abordagem recomendada é usar fatores de correção (multiplique por uma potência adequada de 10 para que a aritmética aconteça 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:Uma solução (muito rápida) se parece com:
Nesse caso:
Definitivamente, recomendo usar uma biblioteca testada como o SinfulJS
fonte
Você está apenas realizando multiplicação? Nesse caso, você pode usar a seu favor um segredo puro sobre aritmética decimal. É isso
NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals
. Isto é, se tivermos0.123 * 0.12
, saberemos que haverá 5 casas decimais porque0.123
tem 3 casas decimais e0.12
duas. Portanto, se o JavaScript nos forneceu um número0.014760000002
, podemos arredondar com segurança para a quinta casa decimal sem medo de perder precisão.fonte
Você está procurando uma
sprintf
implementação para JavaScript, para poder escrever floats com pequenos erros (já que eles são armazenados em formato binário) no formato que você espera.Tente javascript-sprintf , você poderia chamar assim:
para imprimir seu número como um ponto flutuante com duas casas decimais.
Você também pode usar Number.toFixed () para fins de exibição, se preferir não incluir mais arquivos apenas para arredondamento de ponto flutuante até uma determinada precisão.
fonte
Estou descobrindo que o BigNumber.js atende às minhas necessidades.
Possui boa documentação e o autor é muito diligente em responder aos comentários.
O mesmo autor possui 2 outras bibliotecas semelhantes:
Big.js
e Decimal.js
Aqui está um código usando o BigNumber:
fonte
---ou---
---Além disso---
--- como em ---
fonte
Esta função 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 que não seja.
fonte
Surpreendentemente, essa função ainda não foi lançada, embora outras tenham variações semelhantes. É dos documentos da Web MDN para Math.round (). É conciso e permite uma precisão variável.
console.log (precisionRound (1234.5678, 1)); // saída esperada: 1234,6
console.log (precisionRound (1234.5678, -1)); // saída esperada: 1230
ATUALIZAÇÃO: 20 / ago / 2019 Acabei de reparar neste erro. Eu acredito que é devido a um erro de precisão de ponto flutuante com Math.round ().
Essas condições funcionam corretamente:
Consertar:
Isso apenas adiciona um dígito à direita ao arredondar decimais. O MDN atualizou a página Math.round, para que talvez alguém possa fornecer uma solução melhor.
fonte
Você só precisa decidir quantos dígitos decimais realmente deseja - não pode comer o bolo e comê-lo também :-)
Erros numéricos se acumulam a cada operação adicional e, se você não a interromper mais cedo, ela apenas aumentará. As bibliotecas numéricas que apresentam resultados que parecem limpos simplesmente cortam os últimos 2 dígitos a cada etapa; os 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, dividindo e usando pov (...)). Uma boa lib de matemática forneceria o piso (x, n) para fazer o corte para você.
Portanto, no mínimo, você deve tornar global var / constante com pov (10, n) - o que significa que você decidiu a precisão de que precisa :-) Em seguida, faça:
Você também pode continuar fazendo matemática e apenas cortar no final - supondo que você esteja apenas exibindo e não fazendo if-s com resultados. Se você pode fazer isso, então .toFixed (...) pode ser mais eficiente.
Se você estiver fazendo comparações if-s / e não desejar cortar, também precisará de uma pequena constante, geralmente chamada eps, que é uma casa decimal maior que o erro máximo esperado. Digamos que seu ponto de corte tenha as últimas duas casas decimais - então seu eps terá 1 no 3º lugar do último (3º menos significativo) e você poderá usá-lo para comparar se o resultado está dentro do intervalo de eps esperado (0,02 -eps <0,1 * 0,2 <0,02 + eps).
fonte
Math.floor(-2.1)
é-3
. Então talvez usar por exemploMath[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
floor
invés deround
?Você pode usar
parseFloat()
etoFixed()
se desejar ignorar esse problema para uma pequena operação:fonte
A função round () em phpjs.org funciona bem: http://phpjs.org/functions/round
fonte
0,6 * 3 é incrível!)) Para mim, isso funciona bem:
Muito, muito simples))
fonte
8.22e-8 * 1.3
?Observe que, para uso geral, é provável que esse comportamento seja 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:Portanto, em vez de realizar uma comparação como esta
você pode definir uma função de comparação personalizada, assim:
Fonte: http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon
fonte
0.9 !== 0.8999999761581421
O resultado obtido é correto e bastante consistente nas implementações de ponto flutuante em diferentes idiomas, processadores e sistemas operacionais - a única coisa que muda é o nível de imprecisão quando o flutuador é na verdade um duplo (ou superior).
0,1 em pontos flutuantes binários é como 1/3 em decimal (ou seja, 0,3333333333333 ... para sempre), simplesmente não há uma maneira precisa de lidar com isso.
Se você estiver lidando com carros alegóricos, sempre espere pequenos erros de arredondamento, portanto, sempre será necessário arredondar o resultado exibido para algo sensato. Em troca, você obtém aritmética muito, muito rápida e poderosa, porque todos os cálculos 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% das vezes, você simplesmente não precisa da precisão. Se você está lidando com coisas que precisam desse nível de precisão (por exemplo, transações financeiras), o Javascript provavelmente não é a melhor ferramenta a ser usada (como você deseja aplicar os tipos de pontos fixos, uma linguagem estática é provavelmente melhor) )
Você está procurando a solução elegante, então receio que seja isso: os flutuadores são rápidos, mas têm pequenos erros de arredondamento - sempre arredondados para algo sensato ao exibir seus resultados.
fonte
Para evitar isso, você deve trabalhar com valores inteiros em vez de pontos flutuantes. Portanto, quando você quiser ter 2 posições de precisão, trabalhe com os valores * 100, para 3 posições use 1000. Ao exibir, use um formatador para colocar no separador.
Muitos sistemas omitem o trabalho com decimais dessa maneira. Essa é a razão pela qual muitos sistemas trabalham com centavos (como número inteiro) em vez de dólares / euro (como ponto flutuante).
fonte
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 entrada. Os erros nas entradas, obviamente, resultam em erros na saída. No caso de uma função ou operador discreto, pode haver grandes diferenças na saída em torno do ponto em que a função ou operador é discreto.
Entrada e saída para valores de ponto flutuante
Portanto, ao usar variáveis de ponto flutuante, você deve sempre estar ciente disso. E qualquer saída que você deseja de um cálculo com pontos flutuantes deve sempre ser formatada / condicionada antes de exibir com isso em mente.
Quando apenas funções e operadores contínuos são usados, o arredondamento para a precisão desejada geralmente é suficiente (não trunque). Os recursos de formatação padrão usados para converter carros alegóricos em string geralmente fazem isso por 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 entradas e na precisão desejada da saída. Você deve
Essas duas coisas geralmente não são feitas e, na maioria dos casos, as diferenças causadas por não serem feitas 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 ou operadores discretos (como módulos)
Quando operadores ou funções discretas estão envolvidas, podem ser necessárias correções extras para garantir que a saída seja a esperada. Arredondar e adicionar pequenas correções antes do arredondamento não podem resolver o problema.
Pode ser necessária uma verificação / correção especial nos resultados intermediários do cálculo, imediatamente após a aplicação da função ou do operador discreto. Para um caso específico (operador de módulo ), consulte minha resposta na pergunta: Por que o operador de módulo retorna número fracionário em javascript?
Melhor evitar ter o problema
Geralmente, é mais eficiente evitar esses problemas usando tipos de dados (formatos de número inteiro ou de ponto fixo) para cálculos como este, que podem armazenar a entrada esperada sem erros de arredondamento. Um exemplo disso é que você nunca deve usar valores de ponto flutuante para cálculos financeiros.
fonte
Dê uma olhada na aritmética de ponto fixo . Provavelmente resolverá o 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.
fonte
Experimente a minha biblioteca aritmética cilíndrica, que você pode ver aqui . Se você quiser uma versão posterior, posso comprar uma.
fonte
Você não pode representar a maioria das frações decimais exatamente com os 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ários ou um tipo de ponto flutuante com base decimal. Por exemplo, o aplicativo Calculadora que acompanha o Windows agora usa aritmética de precisão arbitrária para resolver esse problema .
fonte
fonte
Você está certo, a razão disso é a precisão limitada dos 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 perda de precisão. Quando se trata de impressão, você pode exibir o resultado como fração. Com a representação que propus, isso se torna trivial.
Claro que isso não ajudará muito em números irracionais. Mas você pode otimizar seus cálculos da maneira que eles causarão o menor problema (por exemplo, detectar situações como
sqrt(3)^2)
.fonte
<pedant>
na verdade, o OP colocá-lo para baixo para operações de ponto flutuante imprecisas, o que está errado</pedant>
Eu tive um problema desagradável de erro de arredondamento com o mod 3. Às vezes, quando eu deveria obter 0, eu recebia 0,000 ... 01. É fácil de manusear, basta testar <= .01. Mas às vezes eu recebia 2.99999999999998. OUCH!
BigNumbers resolveu o problema, mas introduziu outro problema, um tanto irônico. Ao tentar carregar 8,5 no BigNumbers, fui informado de que era realmente 8,4999… e tinha mais de 15 dígitos significativos. Isso significava que a BigNumbers não podia aceitá-lo (acredito que mencionei que esse problema era um tanto irônico).
Solução simples para o problema irônico:
fonte
Use o Número (1.234443) .toFixed (2); ele imprimirá 1,23
fonte
decimal.js , big.js ou bignumber.js podem ser usados para evitar problemas de manipulação de ponto flutuante em Javascript:
link para comparações detalhadas
fonte
Elegante, previsível e reutilizável
Vamos lidar com o problema de maneira elegante e reutilizável. As sete linhas a seguir permitem acessar a precisão do ponto flutuante que você deseja em qualquer número simplesmente anexando
.decimal
ao final do número, fórmula ouMath
função incorporada.Felicidades!
fonte
Usar
fonte
Math.round(x*Math.pow(10,4))/Math.pow(10,4);
. O arredondamento é sempre uma opção, mas eu só queria saber se existe alguma solução melhornão é elegante, mas faz o trabalho (remove zeros à direita)
fonte
Isso funciona para mim:
fonte
Saída usando a seguinte função:
Preste atenção na saída
toFixedCurrency(x)
.fonte