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

620

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.020000000000000004enquanto 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 toFixedou 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.

Juri
fonte
118
Na verdade, o erro ocorre porque não há como mapear 0.1para um número de ponto flutuante binário finito.
Aaron Digulla 22/09/09
10
A maioria das frações não pode ser convertida para um decimal com precisão exata. Uma boa explicação está aqui: docs.python.org/release/2.5.1/tut/node16.html
Nate Zaugg
7
possível duplicata da matemática do JavaScript está quebrada?
epascarello
53
@ SalmanA: O fato de o seu tempo de execução JavaScript esconder esse problema não significa que estou errado.
precisa saber é o seguinte
5
Discordo de Aaron, existem maneiras de codificar 0.1 perfeita e completamente em binário. Mas o IEEE 754 não define necessariamente isso. Imagine uma representação na qual você codificaria a parte inteira em binário, por um lado, a parte decimal, por outro lado, até n decimais, também em binário, como um número inteiro normal> 0 e, finalmente, a posição do ponto decimal . Bem, você representaria 0,1 perfeitamente, sem erros. Aliás, como JS usa um número finito de casas decimais internamente, os desenvolvedores também podem codificar as tripas para não cometer esse erro nas últimas casas decimais.
Fabien Haddadi

Respostas:

469

No 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 que seus resultados sejam somados exatamente, especialmente quando você trabalha com dinheiro: use um tipo de dados decimal especial.
  • Se você não quiser ver todas essas casas decimais extras: simplesmente formate seu resultado arredondado para um número fixo de casas decimais ao exibi-lo.
  • Se você não tiver um tipo de dados decimal disponível, uma alternativa é trabalhar com números inteiros, por exemplo, faça cálculos de dinheiro inteiramente em centavos. Mas isso é mais trabalhoso e tem algumas desvantagens.

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.

Michael Borgwardt
fonte
11
Notei seu link morto para BigDecimal e ao olhar para um espelho, eu encontrei uma alternativa chamada numerogrante: jsfromhell.com/classes/bignumber
Jacksonkr
4
@ bass-t: Sim, mas os flutuadores podem representar exatamente números inteiros até o comprimento do significando, e conforme o padrão ECMA é um flutuador de 64 bits. Assim pode representar exatamente inteiros até 2 ^ 52
Michael Borgwardt
5
@ Karl: A fração decimal 1/10 não pode ser representada como uma fração binária finita na base 2, e é isso que são os números Javascript. Portanto, é de fato exatamente o mesmo problema.
Michael Borgwardt
12
Aprendi hoje que mesmo números inteiros têm problemas de precisão em javascript. Considere que console.log(9332654729891549)realmente imprime 9332654729891548(ou seja, fora por um!)
mlathe
12
@mlathe: Doh .. ;P... Entre 2⁵²= 4,503,599,627,370,496e 2⁵³= 9,007,199,254,740,992os números representáveis ​​são exatamente os números inteiros . Para a próxima faixa, desde 2⁵³que 2⁵⁴, tudo é multiplicado por2 , por isso os números representáveis são os mesmo aqueles , etc. Por outro lado, para a faixa anterior de 2⁵¹que 2⁵², 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 de toPrecision()para valores entre 0e 1).
GitaarLAB 02/03
126

Gosto da solução de Pedro Ladaria e uso algo semelhante.

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

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ê.

linux_mike
fonte
21
Alguma razão pela qual você escolheu 12?
qwertymk
18
toPrecisionretorna uma string em vez de um número. Isso nem sempre pode ser desejável.
SStanley
7
parseFloat (1.005) .toPrecision (3) => 1.00
Peter
5
@ user2428118, eu sei, eu quis mostrar o erro de arredondamento, o resultado é 1,00 em vez de 1,01
Peter
9
O que o @ user2428118 disse pode não ser óbvio o suficiente: (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á-lo toPrecision(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 em toFixed(2)vez disso
aexl 15/11
79

Para 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 é 10e 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:

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

Definitivamente, recomendo usar uma biblioteca testada como o SinfulJS

SheetJS
fonte
1
Il amam esta solução elegante, mas não parece ser perfeito: jsfiddle.net/Dm6F5/1 Math.a (76,65, 38,45) retorna 115.10000000000002
nicolallias
3
Math.m (10,2332226616) está me fornecendo "-19627406800", que é um valor negativo ... Espero que exista um limite superior - pode estar causando esse problema. Por favor, sugerem
Shiva Komuravelly
1
Tudo isso parece ótimo, mas parece ter um ou dois erros em algum lugar.
MrYellow
5
Solução muito rápida, ele disse ... reparo quebrado, ninguém nunca disse.
Cozzbie
2
Não use o código acima. Não é absolutamente uma 'solução rápida' se não funcionar. Esta é uma pergunta relacionada à matemática, portanto é necessária precisão.
Drenai
49

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 tivermos 0.123 * 0.12, saberemos que haverá 5 casas decimais porque 0.123tem 3 casas decimais e 0.12duas. Portanto, se o JavaScript nos forneceu um número 0.014760000002, podemos arredondar com segurança para a quinta casa decimal sem medo de perder precisão.

Nate Zaugg
fonte
6
... e como obter a quantidade exata de casas decimais.
line-o
7
0,5 * 0,2 = 0,10; Você ainda pode truncar com 2 casas decimais (ou menos). Mas nunca haverá um número com significado matemático além desta lei.
Nate Zaugg
3
Você tem uma citação para isso? Observe também que o mesmo não se aplica à divisão.
Griffin
3
@NateZaugg você não pode truncar os decimais transbordantes, você deve arredondar o valor, porque 2090,5 * 8,61 é 17999.205, mas no float é 17999.204999999998
Lostfields
3
@Lostfields - Você está correto! Eu atualizei minha resposta.
Nate Zaugg
29

Você está procurando uma sprintfimplementaçã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:

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

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.

Douglas
fonte
4
Eu acho que essa é a solução mais limpa. A menos que você realmente precise que o resultado seja 0,02, o pequeno erro é insignificante. Parece que o importante é que seu número seja exibido bem, não que você tenha precisão arbitrária.
Long Ouyang
2
Para exibição, essa é realmente a melhor opção; para cálculos complicados, verifique a resposta de Borgwardt.
Não disponível
4
Mas, novamente, isso retornará exatamente a mesma sequência que yourNumber.toFixed (2).
Robert
27

Estou descobrindo que o BigNumber.js atende às minhas necessidades.

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

Possui boa documentação e o autor é muito diligente em responder aos comentários.

O mesmo autor possui 2 outras bibliotecas semelhantes:

Big.js

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

e Decimal.js

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

Aqui está um código usando o 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
fonte
3
Usar uma biblioteca é definitivamente a melhor escolha na minha opinião.
Anthony
1
A partir deste link github.com/MikeMcl/big.js/issues/45 bignumber.js -> decimal.js financeiro -> científico big.js -> ???
vee 25/01
20
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
shawndumas
fonte
4
Eu acho que isso daria o mesmo problema como resultado. Você retorna um ponto flutuante, portanto, uma grande chance de o valor de retorno também estar "incorreto".
Gertjan
1
Muito inteligente e útil, +1.
91316 Jonatas Walker
18

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.

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
fonte
Ai credo. Sim, vamos converter números em strings para matemática de ponto flutuante e também proporemos isso como resposta.
Andrew
17

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.

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)); // saída 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'>

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

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

Essas condições funcionam corretamente:

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

Consertar:

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

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.

HelloWorldPeace
fonte
resposta errada. 10.2 sempre retornará 10.19. jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas
@Ilvinas O link JSBin que você postou não usa a função MDN listada acima. Eu acho que seu comentário é direcionado para a pessoa errada.
HelloWorldPeace
13

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:

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 - 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).

ZXX
fonte
Você também pode adicionar 0,5 para fazer o arredondamento de um homem pobre: ​​Math.floor (x * PREC_LIM + 0.5) / PREC_LIM
cmroanirgo 4/12/12
Note, porém, que por exemplo Math.floor(-2.1)é -3. Então talvez usar por exemploMath[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
MikeM
Por que ao floorinvés de round?
Quinn Comendant
12

Você pode usar parseFloat()e toFixed()se desejar 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((a-b).toFixed(2));

c = 0.1;
Softwareddy
fonte
11

A função round () em phpjs.org funciona bem: http://phpjs.org/functions/round

num = .01 + .06;  // yields 0.0699999999999
rnum = round(num,12); // yields 0.07
Tom
fonte
2
@jrg Por convenção, os números que terminam com um "5" são arredondados para o par mais próximo (porque sempre arredondar para cima ou para baixo introduziria um viés nos resultados). Portanto, 4.725 arredondado para duas casas decimais deve realmente ser 4,72.
Mark A. Durham
9

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

Олег Всильдеревьев
fonte
Isso funcionaria com algo assim 8.22e-8 * 1.3?
Paul Carlton
0,6 x 3 = 1,8, o código que você fornece para 2 ... então não é bom.
Zyo
@Zyo Retorna 1,8 nesta instância. Como você o executou?
Drenai 12/09
Interessante. Você pode trocar os operadores de multiplicação e divisão nisso e também funciona.
Andrew
9

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

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

você pode definir uma função 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

user10089632
fonte
No meu caso, Number.EPSILON era muito pequeno, o que resultou em, por exemplo,0.9 !== 0.8999999761581421
Tom Tom
8

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.

Keith
fonte
8

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).

Gertjan
fonte
7

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

  • Arredonde as entradas para a precisão esperada ou verifique se nenhum valor pode ser inserido com maior precisão.
  • Adicione um valor pequeno às saídas antes de arredondá-las / formatá-las, menor ou igual a 1/4 da precisão desejada e maior que o erro máximo esperado causado por erros de arredondamento na entrada 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 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.

Stefan Mondelaers
fonte
4

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.

Marius
fonte
5
O problema não é ponto flutuante vs. ponto fixo, o problema é binário vs. decimal.
Michael Borgwardt
4

Experimente a minha biblioteca aritmética cilíndrica, que você pode ver aqui . Se você quiser uma versão posterior, posso comprar uma.

Robert L
fonte
4

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 .

MSN
fonte
4

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
Ashish Singhal
fonte
3

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).

skalee
fonte
Você está certo, a razão para isso é a precisão de números de ponto flutuante - <pedant>na verdade, o OP colocá-lo para baixo para operações de ponto flutuante imprecisas, o que está errado</pedant>
detly
3

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:

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
fonte
2

Use o Número (1.234443) .toFixed (2); ele imprimirá 1,23

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

decimal.js , big.js ou bignumber.js podem ser usados ​​para evitar problemas de manipulação de ponto flutuante em 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; precisão especificada em casas decimais; precisão aplicada somente à divisão.

bignumber.js: bases 2-64; opções de configuração; NaN; Infinidade; precisão especificada em casas decimais; precisão aplicada somente à divisão; prefixos base.

decimal.js: bases 2-64; opções de configuração; NaN; Infinidade; poderes não inteiros, exp, ln, log; precisão especificada em dígitos significativos; precisão sempre aplicada; Números aleatórios.

link para comparações detalhadas

Erikas Pliauksta
fonte
2

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 .decimalao final do número, fórmula ou Mathfunção 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

Felicidades!

Bernesto
fonte
2
Se você optar por reduzir o voto, pelo menos forneça um motivo.
Bernesto 30/09/19
1

Usar

var x = 0.1*0.2;
 x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);
Himadri
fonte
4
Hmm ... mas observe, isso sempre arredonda para 2 casas decimais. Claro que isso seria uma opção, mas e o cálculo 0,55 * 0,55 (já que não sei os números exatos com antecedência. Isso daria 0,3 em vez de 0,3025. É claro que eu poderia usar 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 melhor
Juri
10,2 sempre retorna 10,19 jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas
1

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

var num = 0.1*0.2;
alert(parseFloat(num.toFixed(10))); // shows 0.02
Pedro
fonte
6
O toFixed nem sempre funciona: stackoverflow.com/questions/661562/…
Sam Hasler
1

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
Antonio Max
fonte
1
infelizmente, round_up (4.15,2) => 4.16.
Jrg
1

Saída usando a seguinte função:

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).

Júlio Paulillo
fonte