Sempre me disseram para nunca representar dinheiro com double
ou com float
tipos, e desta vez faço a pergunta para você: por quê?
Tenho certeza de que há uma boa razão, simplesmente não sei o que é.
Sempre me disseram para nunca representar dinheiro com double
ou com float
tipos, e desta vez faço a pergunta para você: por quê?
Tenho certeza de que há uma boa razão, simplesmente não sei o que é.
Respostas:
Porque floats e doubles não podem representar com precisão os múltiplos da base 10 que usamos para dinheiro. Esse problema não é apenas para Java, é para qualquer linguagem de programação que use tipos de ponto flutuante base 2.
Na base 10, você pode escrever 10,25 como 1025 * 10 -2 (um número inteiro vezes a potência de 10). Os números de ponto flutuante IEEE-754 são diferentes, mas uma maneira muito simples de pensar sobre eles é multiplicar por uma potência de dois. Por exemplo, você pode observar 164 * 2 -4 (um número inteiro vezes a potência de dois), que também é igual a 10,25. Não é assim que os números são representados na memória, mas as implicações matemáticas são as mesmas.
Mesmo na base 10, essa notação não pode representar com precisão a maioria das frações simples. Por exemplo, você não pode representar 1/3: a representação decimal está se repetindo (0,3333 ...); portanto, não há um número inteiro finito que possa ser multiplicado por uma potência de 10 para obter 1/3. Você pode optar por uma longa sequência de 3 e um pequeno expoente, como 333333333 * 10-10 , mas não é preciso: se você multiplicar por 3, não receberá 1.
No entanto, com o objetivo de contar dinheiro, pelo menos para países cujo dinheiro é avaliado dentro de uma ordem de magnitude do dólar americano, geralmente tudo o que você precisa é ser capaz de armazenar múltiplos de 10 -2 , então isso realmente não importa que 1/3 não pode ser representado.
O problema com números flutuantes e duplos é que a grande maioria dos números semelhantes a dinheiro não tem uma representação exata como um número inteiro vezes uma potência de 2. De fato, os únicos múltiplos de 0,01 entre 0 e 1 (que são significativos ao lidar com dinheiro porque eles são centavos inteiros) que podem ser representados exatamente como um número de ponto flutuante binário IEEE-754 são 0, 0,25, 0,5, 0,75 e 1. Todos os outros são desativados por uma pequena quantia. Como uma analogia ao exemplo 0,333333, se você pegar o valor do ponto flutuante para 0,1 e multiplicá-lo por 10, não receberá 1.
Representar o dinheiro como double
ou float
provavelmente parecerá bom no começo, à medida que o software encerra os pequenos erros, mas à medida que você executa mais adições, subtrações, multiplicações e divisões em números inexatos, os erros serão compostos e você terá valores visivelmente visíveis. não é preciso. Isso torna flutuações e duplas inadequadas para lidar com dinheiro, onde é necessária uma precisão perfeita para múltiplos de poderes da base 10.
Uma solução que funciona em praticamente qualquer idioma é usar números inteiros e contar centavos. Por exemplo, 1025 seria $ 10,25. Vários idiomas também possuem tipos internos para lidar com dinheiro. Entre outros, Java tem a BigDecimal
classe e C # tem o decimal
tipo
1.0 / 10 * 10
pode não ser o mesmo que 1.0.
De Bloch, J., Effective Java, 2ª ed. Item 48:
Os tipos
float
edouble
são particularmente inadequados para cálculos monetários porque é impossível representar 0,1 (ou qualquer outro poder negativo de dez) comofloat
oudouble
exatamente.Por exemplo, suponha que você tenha US $ 1,03 e gaste 42c. Quanto você tem?
System.out.println(1.03 - .42);
imprime
0.6100000000000001
.O caminho certo para resolver este problema é usar
BigDecimal
,int
oulong
para cálculos monetários.
Embora BigDecimal
tenha algumas advertências (consulte a resposta atualmente aceita).
long a = 104
e conta em centavos em vez de dólares.
BigDecimal
.
Isso não é uma questão de precisão, nem é uma questão de precisão. É uma questão de atender às expectativas de humanos que usam a base 10 para cálculos em vez da base 2. Por exemplo, o uso de duplicatas para cálculos financeiros não produz respostas "erradas" no sentido matemático, mas pode produzir respostas que são não o que é esperado em um sentido financeiro.
Mesmo se você arredondar seus resultados no último minuto antes da saída, ainda poderá obter resultados ocasionalmente usando duplas que não correspondam às expectativas.
Usando uma calculadora ou calculando os resultados manualmente, 1,40 * 165 = 231 exatamente. No entanto, usando internamente dobros, no meu ambiente de compilador / sistema operacional, ele é armazenado como um número binário próximo a 230.99999 ... portanto, se você truncar o número, obterá 230 em vez de 231. Você pode considerar que o arredondamento em vez de truncamento seria deram o resultado desejado de 231. Isso é verdade, mas o arredondamento sempre envolve truncamento. Qualquer que seja a técnica de arredondamento que você use, ainda existem condições de contorno como essa que serão arredondadas quando você espera que ela seja arredondada. Eles são raros o suficiente para não serem encontrados por meio de testes ou observações casuais. Pode ser necessário escrever algum código para procurar exemplos que ilustrem resultados que não se comportam conforme o esperado.
Suponha que você queira arredondar algo para o centavo mais próximo. Então você pega o resultado final, multiplica por 100, adiciona 0,5, trunca e depois divide o resultado por 100 para voltar aos centavos. Se o número interno armazenado for 3,46499999 .... em vez de 3,465, você receberá 3,46 em vez de 3,47 quando arredondar o número para o centavo mais próximo. Mas seus cálculos de base 10 podem ter indicado que a resposta deve ser exatamente 3.465, o que claramente deve arredondar para 3,47, e não 3,46. Ocasionalmente, esses tipos de coisas acontecem na vida real quando você usa duplas para cálculos financeiros. É raro, portanto, muitas vezes passa despercebido como um problema, mas acontece.
Se você usar a base 10 para seus cálculos internos, em vez de duplicar, as respostas serão sempre exatamente o que é esperado pelos humanos, assumindo que não há outros erros no seu código.
Math.round(0.49999999999999994)
retornar 1?
Estou preocupado com algumas dessas respostas. Acho que duplas e carros alegóricos têm um lugar nos cálculos financeiros. Certamente, ao adicionar e subtrair valores monetários não fracionários, não haverá perda de precisão ao usar classes inteiras ou BigDecimal. Mas, ao executar operações mais complexas, você geralmente obtém resultados com várias ou várias casas decimais, independentemente de como armazena os números. A questão é como você apresenta o resultado.
Se o resultado estiver na fronteira entre ser arredondado para cima e para baixo, e esse último centavo realmente importa, você provavelmente deve estar dizendo ao espectador que a resposta está quase no meio - exibindo mais casas decimais.
O problema com duplos e, mais ainda, com carros alegóricos, é quando eles são usados para combinar grandes números e pequenos números. Em java,
System.out.println(1000000.0f + 1.2f - 1000000.0f);
resulta em
1.1875
Carros alegóricos e duplos são aproximados. Se você criar um BigDecimal e passar um float para o construtor, verá o que o float realmente é igual:
groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375
provavelmente não é assim que você deseja representar US $ 1,01.
O problema é que a especificação IEEE não tem uma maneira exata de representar todas as frações, algumas delas acabam como frações repetidas e você acaba com erros de aproximação. Como os contadores gostam que as coisas saiam exatamente ao centavo, e os clientes ficam irritados se pagarem a conta e, após o processamento do pagamento, eles devem .01 e recebem uma taxa ou não conseguem fechar a conta, é melhor usar tipos exatos como decimal (em C #) ou java.math.BigDecimal em Java.
Não é que o erro não seja controlável se você arredondar: veja este artigo de Peter Lawrey . É mais fácil não ter que arredondar em primeiro lugar. A maioria dos aplicativos que lidam com dinheiro não exige muita matemática, as operações consistem em adicionar itens ou alocar valores a diferentes baldes. A introdução de ponto flutuante e arredondamento apenas complica as coisas.
float
, double
e BigDecimal
são valores exatos . A conversão de código em objeto é inexata e outras operações. Os tipos em si não são inexatos.
Correrei o risco de ter o voto negativo, mas acho que a inadequação de números de ponto flutuante para cálculos de moeda é superestimada. Desde que você faça o arredondamento de centavos corretamente e tenha dígitos significativos suficientes para trabalhar, a fim de combater a incompatibilidade de representação decimal binária explicada pelo zneak, não haverá problema.
As pessoas que calculam com moeda no Excel sempre usaram flutuadores de precisão dupla (não há tipo de moeda no Excel) e ainda tenho que ver alguém reclamando de erros de arredondamento.
Claro, você tem que ficar dentro da razão; por exemplo, uma loja virtual simples provavelmente nunca enfrentaria nenhum problema com flutuadores de precisão dupla, mas se você fizer, por exemplo, contabilidade ou qualquer outra coisa que exija a adição de uma quantidade grande (irrestrita) de números, você não desejaria tocar em números de ponto flutuante com um metro e meio. pólo.
Embora seja verdade que o tipo de ponto flutuante possa representar apenas dados aproximadamente decimais, também é verdade que, se arredondarmos os números para a precisão necessária antes de apresentá-los, obteremos o resultado correto. Usualmente.
Geralmente porque o tipo duplo tem uma precisão menor que 16 algarismos. Se você precisar de melhor precisão, não é um tipo adequado. Também aproximações podem se acumular.
Deve-se dizer que, mesmo se você usar aritmética de ponto fixo, ainda precisará arredondar números, não foi o fato de BigInteger e BigDecimal cometerem erros se você obtiver números decimais periódicos. Portanto, há uma aproximação também aqui.
Por exemplo, o COBOL, usado historicamente para cálculos financeiros, tem uma precisão máxima de 18 dígitos. Portanto, geralmente há um arredondamento implícito.
Concluindo, na minha opinião, o duplo é inadequado principalmente pela precisão de 16 dígitos, que pode ser insuficiente, não porque é aproximado.
Considere a seguinte saída do programa subseqüente. Isso mostra que, após o arredondamento duplo, obtém o mesmo resultado que BigDecimal com precisão 16.
Precision 14
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.000051110111115611
Double : 56789.012345 / 1111111111 = 0.000051110111115611
Precision 15
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.0000511101111156110
Double : 56789.012345 / 1111111111 = 0.0000511101111156110
Precision 16
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.00005111011111561101
Double : 56789.012345 / 1111111111 = 0.00005111011111561101
Precision 17
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.000051110111115611011
Double : 56789.012345 / 1111111111 = 0.000051110111115611013
Precision 18
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.0000511101111156110111
Double : 56789.012345 / 1111111111 = 0.0000511101111156110125
Precision 19
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.00005111011111561101111
Double : 56789.012345 / 1111111111 = 0.00005111011111561101252
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.MathContext;
public class Exercise {
public static void main(String[] args) throws IllegalArgumentException,
SecurityException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
String amount = "56789.012345";
String quantity = "1111111111";
int [] precisions = new int [] {14, 15, 16, 17, 18, 19};
for (int i = 0; i < precisions.length; i++) {
int precision = precisions[i];
System.out.println(String.format("Precision %d", precision));
System.out.println("------------------------------------------------------");
execute("BigDecimalNoRound", amount, quantity, precision);
execute("DoubleNoRound", amount, quantity, precision);
execute("BigDecimal", amount, quantity, precision);
execute("Double", amount, quantity, precision);
System.out.println();
}
}
private static void execute(String test, String amount, String quantity,
int precision) throws IllegalArgumentException, SecurityException,
IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
Method impl = Exercise.class.getMethod("divideUsing" + test, String.class,
String.class, int.class);
String price;
try {
price = (String) impl.invoke(null, amount, quantity, precision);
} catch (InvocationTargetException e) {
price = e.getTargetException().getMessage();
}
System.out.println(String.format("%-30s: %s / %s = %s", test, amount,
quantity, price));
}
public static String divideUsingDoubleNoRound(String amount,
String quantity, int precision) {
// acceptance
double amount0 = Double.parseDouble(amount);
double quantity0 = Double.parseDouble(quantity);
//calculation
double price0 = amount0 / quantity0;
// presentation
String price = Double.toString(price0);
return price;
}
public static String divideUsingDouble(String amount, String quantity,
int precision) {
// acceptance
double amount0 = Double.parseDouble(amount);
double quantity0 = Double.parseDouble(quantity);
//calculation
double price0 = amount0 / quantity0;
// presentation
MathContext precision0 = new MathContext(precision);
String price = new BigDecimal(price0, precision0)
.toString();
return price;
}
public static String divideUsingBigDecimal(String amount, String quantity,
int precision) {
// acceptance
BigDecimal amount0 = new BigDecimal(amount);
BigDecimal quantity0 = new BigDecimal(quantity);
MathContext precision0 = new MathContext(precision);
//calculation
BigDecimal price0 = amount0.divide(quantity0, precision0);
// presentation
String price = price0.toString();
return price;
}
public static String divideUsingBigDecimalNoRound(String amount, String quantity,
int precision) {
// acceptance
BigDecimal amount0 = new BigDecimal(amount);
BigDecimal quantity0 = new BigDecimal(quantity);
//calculation
BigDecimal price0 = amount0.divide(quantity0);
// presentation
String price = price0.toString();
return price;
}
}
O resultado do número de ponto flutuante não é exato, o que os torna inadequados para qualquer cálculo financeiro que exija resultado exato e não aproximação. float e double são projetados para cálculos científicos e de engenharia e muitas vezes não produzem resultado exato, também o resultado do cálculo de ponto flutuante pode variar de JVM para JVM. Veja o exemplo abaixo de BigDecimal e primitivo duplo, que é usado para representar o valor monetário, é claro que o cálculo de ponto flutuante pode não ser exato e deve-se usar o BigDecimal para cálculos financeiros.
// floating point calculation
final double amount1 = 2.0;
final double amount2 = 1.1;
System.out.println("difference between 2.0 and 1.1 using double is: " + (amount1 - amount2));
// Use BigDecimal for financial calculation
final BigDecimal amount3 = new BigDecimal("2.0");
final BigDecimal amount4 = new BigDecimal("1.1");
System.out.println("difference between 2.0 and 1.1 using BigDecimal is: " + (amount3.subtract(amount4)));
Resultado:
difference between 2.0 and 1.1 using double is: 0.8999999999999999
difference between 2.0 and 1.1 using BigDecimal is: 0.9
double
FP binário para o centavo não teria problemas em calcular para 0,5 centavo, assim como FP decimal. Se os cálculos de ponto flutuante gerarem um valor de juros de, por exemplo, 123.499941 ¢, por meio de FP binário ou FP decimal, o problema do arredondamento duplo é o mesmo - sem nenhuma vantagem. Sua premissa parece assumir o valor matematicamente preciso e o FP decimal é o mesmo - algo que mesmo o FP decimal não garante. 0.5 / 7.0 * 7.0 é um problema para FP binário e deicmal. IAC, a maioria será discutida, pois espero que a próxima versão do C forneça FP decimal.
Como dito anteriormente "Representar dinheiro como um double ou float provavelmente parecerá bom a princípio, pois o software encerra pequenos erros, mas à medida que você executa mais adições, subtrações, multiplicações e divisões em números inexatos, você perde mais e mais precisão à medida que os erros aumentam. Isso torna flutuações e duplas inadequadas para lidar com dinheiro, onde é necessária precisão perfeita para múltiplos de poderes da base 10 ".
Finalmente, o Java tem uma maneira padrão de trabalhar com moeda e dinheiro!
JSR 354: API de Dinheiro e Moeda
O JSR 354 fornece uma API para representar, transportar e executar cálculos abrangentes com dinheiro e moeda. Você pode baixá-lo neste link:
JSR 354: Download da API de Dinheiro e Moeda
A especificação consiste no seguinte:
- Uma API para lidar com, por exemplo, valores monetários e moedas
- APIs para suportar implementações intercambiáveis
- Fábricas para criar instâncias das classes de implementação
- Funcionalidade para cálculos, conversão e formatação de valores monetários
- API Java para trabalhar com Money and Currencies, planejada para ser incluída no Java 9.
- Todas as classes e interfaces de especificação estão localizadas no pacote javax.money. *.
Exemplos de exemplo da JSR 354: API Money e Currency:
Um exemplo de criação de um MonetaryAmount e impressão no console é semelhante a este ::
MonetaryAmountFactory<?> amountFactory = Monetary.getDefaultAmountFactory();
MonetaryAmount monetaryAmount = amountFactory.setCurrency(Monetary.getCurrency("EUR")).setNumber(12345.67).create();
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
Ao usar a API de implementação de referência, o código necessário é muito mais simples:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
A API também suporta cálculos com MonetaryAmounts:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));
CurrencyUnit e MonetaryAmount
// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);
MonetaryAmount possui vários métodos que permitem acessar a moeda atribuída, o valor numérico, sua precisão e muito mais:
MonetaryAmount monetaryAmount = Money.of(123.45, euro);
CurrencyUnit currency = monetaryAmount.getCurrency();
NumberValue numberValue = monetaryAmount.getNumber();
int intValue = numberValue.intValue(); // 123
double doubleValue = numberValue.doubleValue(); // 123.45
long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100
long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45
int precision = numberValue.getPrecision(); // 5
// NumberValue extends java.lang.Number.
// So we assign numberValue to a variable of type Number
Number number = numberValue;
As MonetaryAmounts podem ser arredondadas usando um operador de arredondamento:
CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD");
MonetaryAmount dollars = Money.of(12.34567, usd);
MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd);
MonetaryAmount roundedDollars = dollars.with(roundingOperator); // USD 12.35
Ao trabalhar com coleções de MonetaryAmounts, estão disponíveis alguns métodos úteis de filtragem, classificação e agrupamento.
List<MonetaryAmount> amounts = new ArrayList<>();
amounts.add(Money.of(2, "EUR"));
amounts.add(Money.of(42, "USD"));
amounts.add(Money.of(7, "USD"));
amounts.add(Money.of(13.37, "JPY"));
amounts.add(Money.of(18, "USD"));
Operações personalizadas MonetaryAmount
// A monetary operator that returns 10% of the input MonetaryAmount
// Implemented using Java 8 Lambdas
MonetaryOperator tenPercentOperator = (MonetaryAmount amount) -> {
BigDecimal baseAmount = amount.getNumber().numberValue(BigDecimal.class);
BigDecimal tenPercent = baseAmount.multiply(new BigDecimal("0.1"));
return Money.of(tenPercent, amount.getCurrency());
};
MonetaryAmount dollars = Money.of(12.34567, "USD");
// apply tenPercentOperator to MonetaryAmount
MonetaryAmount tenPercentDollars = dollars.with(tenPercentOperator); // USD 1.234567
Recursos:
Manipulando Dinheiro e Moedas em Java com JSR 354
Analisando a API Java 9 Money and Currency (JSR 354)
Veja também: JSR 354 - Moeda e Dinheiro
Se o seu cálculo envolver várias etapas, a aritmética de precisão arbitrária não cobrirá 100%.
A única maneira confiável de usar uma representação perfeita dos resultados (use um tipo de dados de Fração personalizado que irá agrupar as operações de divisão em lote até a última etapa) e apenas converter em notação decimal na última etapa.
A precisão arbitrária não ajuda, porque sempre pode haver números com tantas casas decimais ou alguns resultados como 0.6666666
... Nenhuma representação arbitrária abordará o último exemplo. Então você terá pequenos erros em cada etapa.
Esses erros serão adicionados, eventualmente, não será mais fácil ignorar. Isso é chamado de propagação de erros .
A maioria das respostas destacou as razões pelas quais não se deve usar dobras para cálculos de dinheiro e moeda. E eu concordo totalmente com eles.
Isso não significa que os duplos nunca possam ser usados para esse fim.
Eu trabalhei em vários projetos com requisitos muito baixos de GC, e ter objetos BigDecimal foi um grande contribuinte para essa sobrecarga.
É a falta de entendimento sobre a dupla representação e a falta de experiência em lidar com a exatidão e precisão que traz essa sugestão sábia.
Você pode fazê-lo funcionar se for capaz de lidar com os requisitos de precisão e exatidão do seu projeto, o que deve ser feito com base em qual faixa de valores duplos é uma delas.
Você pode consultar o método FuzzyCompare da goiaba para ter uma idéia melhor. A tolerância do parâmetro é a chave. Lidamos com esse problema para um aplicativo de negociação de valores mobiliários e fizemos uma pesquisa exaustiva sobre quais tolerâncias usar para diferentes valores numéricos em diferentes faixas.
Além disso, pode haver situações em que você é tentado a usar wrappers Double como uma chave de mapa, com o mapa de hash sendo a implementação. É muito arriscado porque Double.equals e código de hash, por exemplo, valores "0.5" e "0.6 - 0.1" causarão uma grande bagunça.
Muitas das respostas postadas nesta pergunta discutem o IEEE e os padrões em torno da aritmética de ponto flutuante.
Vindo de uma formação que não seja de informática (física e engenharia), tenho a tendência de analisar os problemas de uma perspectiva diferente. Para mim, a razão pela qual eu não usaria um double ou float em um cálculo matemático é que perderia muita informação.
Quais são as alternativas? Existem muitos (e muitos mais dos quais não estou ciente!).
BigDecimal em Java é nativo da linguagem Java. O Apfloat é outra biblioteca de precisão arbitrária para Java.
O tipo de dados decimal em C # é a alternativa .NET da Microsoft para 28 números significativos.
O SciPy (Scientific Python) provavelmente também pode lidar com cálculos financeiros (eu não tentei, mas suspeito que sim).
A GNU Multiple Precision Library (GMP) e a GNU MFPR Library são dois recursos livres e de código aberto para C e C ++.
Também existem bibliotecas de precisão numérica para JavaScript (!) E acho que o PHP pode lidar com cálculos financeiros.
Também existem soluções proprietárias (principalmente para Fortran) e de código aberto, bem como para muitas linguagens de computador.
Não sou cientista da computação treinando. No entanto, eu tendem a inclinar-se para BigDecimal em Java ou decimal em C #. Não tentei as outras soluções que listei, mas provavelmente também são muito boas.
Para mim, eu gosto do BigDecimal por causa dos métodos que ele suporta. O decimal do C # é muito bom, mas não tive a chance de trabalhar com ele tanto quanto gostaria. Faço cálculos científicos de interesse para mim no meu tempo livre e o BigDecimal parece funcionar muito bem porque posso definir a precisão dos meus números de ponto flutuante. A desvantagem do BigDecimal? Às vezes, pode ser lento, especialmente se você estiver usando o método de divisão.
Você pode, rapidamente, procurar nas bibliotecas gratuitas e proprietárias em C, C ++ e Fortran.
Para adicionar respostas anteriores, há também a opção de implementar o Joda-Money em Java, além do BigDecimal, ao lidar com o problema abordado na pergunta. O nome do módulo Java é org.joda.money.
Requer o Java SE 8 ou posterior e não possui dependências.
Para ser mais preciso, há dependência em tempo de compilação, mas não é necessária.
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
</dependency>
Exemplos de uso do Joda Money:
// create a monetary value
Money money = Money.parse("USD 23.87");
// add another amount with safe double conversion
CurrencyUnit usd = CurrencyUnit.of("USD");
money = money.plus(Money.of(usd, 12.43d));
// subtracts an amount in dollars
money = money.minusMajor(2);
// multiplies by 3.5 with rounding
money = money.multipliedBy(3.5d, RoundingMode.DOWN);
// compare two amounts
boolean bigAmount = money.isGreaterThan(dailyWage);
// convert to GBP using a supplied rate
BigDecimal conversionRate = ...; // obtained from code outside Joda-Money
Money moneyGBP = money.convertedTo(CurrencyUnit.GBP, conversionRate, RoundingMode.HALF_UP);
// use a BigMoney for more complex calculations where scale matters
BigMoney moneyCalc = money.toBigMoney();
Documentação: http://joda-money.sourceforge.net/apidocs/org/joda/money/Money.html
Exemplos de implementação: https://www.programcreek.com/java-api-examples/?api=org.joda.money.Money
Algum exemplo ... isso funciona (na verdade não funciona como o esperado), em quase qualquer linguagem de programação ... Eu tentei com Delphi, VBScript, Visual Basic, JavaScript e agora com Java / Android:
double total = 0.0;
// do 10 adds of 10 cents
for (int i = 0; i < 10; i++) {
total += 0.1; // adds 10 cents
}
Log.d("round problems?", "current total: " + total);
// looks like total equals to 1.0, don't?
// now, do reverse
for (int i = 0; i < 10; i++) {
total -= 0.1; // removes 10 cents
}
// looks like total equals to 0.0, don't?
Log.d("round problems?", "current total: " + total);
if (total == 0.0) {
Log.d("round problems?", "is total equal to ZERO? YES, of course!!");
} else {
Log.d("round problems?", "is total equal to ZERO? NO... thats why you should not use Double for some math!!!");
}
RESULTADO:
round problems?: current total: 0.9999999999999999
round problems?: current total: 2.7755575615628914E-17
round problems?: is total equal to ZERO? NO... thats why you should not use Double for some math!!!
Float é uma forma binária de decimal com design diferente; são duas coisas diferentes. Existem pequenos erros entre dois tipos quando convertidos um no outro. Além disso, o float é projetado para representar um número infinito de valores científicos. Isso significa que ele foi projetado para perder precisão para números extremamente pequenos e grandes com esse número fixo de bytes. Decimal não pode representar um número infinito de valores; ele limita apenas a esse número de dígitos decimais. Então Float e Decimal são para propósitos diferentes.
Existem algumas maneiras de gerenciar o erro do valor da moeda:
Use inteiro longo e conte em centavos.
Use precisão dupla, mantenha seus dígitos significativos em 15 apenas para que o decimal possa ser exatamente simulado. Rodada antes de apresentar valores; Arredonde frequentemente ao fazer cálculos.
Use uma biblioteca decimal como Java BigDecimal para não precisar usar o dobro para simular o decimal.
ps: é interessante saber que a maioria das marcas de calculadoras científicas portáteis funciona em decimal em vez de em float. Portanto, nenhuma reclamação apresenta erros de conversão.