Você provavelmente deve escalar seus valores decimais em 100 e representar todos os valores monetários em centavos inteiros. Isso é para evitar problemas com a lógica de ponto flutuante e a aritmética . Não há tipo de dados decimal no JavaScript - o único tipo de dado numérico é de ponto flutuante. Portanto, geralmente é recomendável lidar com dinheiro como 2550
centavos em vez de 25.50
dólares.
Considere isso em JavaScript:
var result = 1.0 + 2.0; // (result === 3.0) returns true
Mas:
var result = 0.1 + 0.2; // (result === 0.3) returns false
A expressão 0.1 + 0.2 === 0.3
retorna false
, mas felizmente a aritmética inteira em ponto flutuante é exata, portanto, erros de representação decimal podem ser evitados escalando 1 .
Observe que, embora o conjunto de números reais seja infinito, apenas um número finito deles (18.437.736.874.454.810.627 para ser exato) pode ser representado exatamente pelo formato de ponto flutuante do JavaScript. Portanto, a representação dos outros números será uma aproximação do número real 2 .
1 Douglas Crockford: JavaScript: As peças boas : Apêndice A - Peças horríveis (página 105) .
2 David Flanagan: JavaScript: The Definitive Guide, quarta edição : 3.1.3 Literais de ponto flutuante (página 31) .
3000.57 + 0.11 === 3000.68
retornafalse
.Escalar cada valor em 100 é a solução. Fazer isso manualmente é provavelmente inútil, pois você pode encontrar bibliotecas que fazem isso por você. Eu recomendo o moneysafe, que oferece uma API funcional adequada para aplicativos ES6:
https://github.com/ericelliott/moneysafe
Funciona no Node.js e no navegador.
fonte
in$, $
nomes dos valores são ambíguos para alguém que não usou o pacote anteriormente. Eu sei que foi a escolha de Eric nomear as coisas dessa maneira, mas ainda acho que é um erro suficiente que eu provavelmente as renomeie na declaração de importação / desestruturação.Money$afe has not yet been tested in production at scale.
. Apenas apontar isso para que qualquer pessoa pode então considerar se isso é apropriado para o seu caso de usoNão existe cálculo financeiro "preciso" por causa de apenas dois dígitos da fração decimal, mas esse é um problema mais geral.
Em JavaScript, você pode dimensionar cada valor em 100 e usar
Math.round()
sempre que uma fração pode ocorrer.Você pode usar um objeto para armazenar os números e incluir o arredondamento em seu
valueOf()
método de protótipo . Como isso:Dessa forma, sempre que você usar um objeto Money, ele será representado como arredondado para duas casas decimais. O valor não arredondado ainda é acessível via
m.amount
.Você pode criar seu próprio algoritmo de arredondamento
Money.prototype.valueOf()
, se quiser.fonte
Money(0.1)
significa que o lexer JavaScript lê a string "0.1" da fonte e a converte em um ponto flutuante binário e você já fez um arredondamento não intencional. O problema é sobre representação (binária vs decimal), não sobre precisão .use decimaljs ... É uma biblioteca muito boa que resolve uma parte dura do problema ...
basta usá-lo em todas as suas operações.
https://github.com/MikeMcl/decimal.js/
fonte
Seu problema decorre da imprecisão nos cálculos de ponto flutuante. Se você estiver usando o arredondamento para resolver isso, terá um erro maior ao multiplicar e dividir.
A solução está abaixo, segue uma explicação:
Você precisará pensar em matemática por trás disso para entender. Números reais como 1/3 não podem ser representados em matemática com valores decimais, pois são infinitos (por exemplo, - .333333333333333 ...). Alguns números em decimal não podem ser representados em binário corretamente. Por exemplo, 0,1 não pode ser representado no binário corretamente com um número limitado de dígitos.
Para uma descrição mais detalhada, consulte aqui: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
Dê uma olhada na implementação da solução: http://floating-point-gui.de/languages/javascript/
fonte
Devido à natureza binária de sua codificação, alguns números decimais não podem ser representados com precisão perfeita. Por exemplo
Se você precisar usar javascript puro, precisará pensar na solução para cada cálculo. Para o código acima, podemos converter decimais em números inteiros.
Evitando problemas com a matemática decimal em JavaScript
Existe uma biblioteca dedicada para cálculos financeiros com ótima documentação. Finance.js
fonte
Infelizmente, todas as respostas até agora ignoram o fato de que nem todas as moedas têm 100 subunidades (por exemplo, o centavo é a subunidade do dólar dos EUA (USD)). Moedas como o Dinar Iraquiano (IQD) possuem 1000 subunidades: um Dinar Iraquiano possui 1000 fils. O iene japonês (JPY) não possui subunidades. Portanto, "multiplique por 100 para fazer aritmética inteira" nem sempre é a resposta correta.
Além disso, para cálculos monetários, você também precisa acompanhar a moeda. Você não pode adicionar um dólar americano (USD) a uma rúpia indiana (INR) (sem antes converter um para o outro).
Também há limitações na quantidade máxima que pode ser representada pelo tipo de dados inteiro do JavaScript.
Nos cálculos monetários, você também deve ter em mente que o dinheiro tem precisão finita (normalmente de 0 a 3 pontos decimais) e o arredondamento precisa ser feito de maneiras específicas (por exemplo, arredondamento "normal" versus arredondamento de banqueiros). O tipo de arredondamento a ser executado também pode variar de acordo com a jurisdição / moeda.
Como lidar com dinheiro em javascript tem uma discussão muito boa dos pontos relevantes.
Nas minhas pesquisas, encontrei a biblioteca dinero.js que aborda muitos dos problemas por meio de cálculos monetários . Ainda não o utilizou em um sistema de produção, portanto não pode dar uma opinião informada sobre ele.
fonte