Que tipo de dados você deve usar para obter dinheiro em Java?
Que tipo de dados você deve usar para obter dinheiro em Java?
Respostas:
Java possui uma Currency
classe que representa os códigos de moeda ISO 4217.
BigDecimal
é o melhor tipo para representar valores decimais da moeda.
Joda Money forneceu uma biblioteca para representar dinheiro.
Você pode usar a API Money e Currency (JSR 354) . Você pode usar essa API, desde que adicione dependências apropriadas ao seu projeto.
Para Java 8, inclua a seguinte implementação de referência como uma dependência no seu pom.xml
:
<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>1.0</version>
</dependency>
Essa dependência será adicionada transitivamente javax.money:money-api
como uma dependência.
Você pode usar a API:
package com.example.money;
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import java.util.Locale;
import javax.money.Monetary;
import javax.money.MonetaryAmount;
import javax.money.MonetaryRounding;
import javax.money.format.MonetaryAmountFormat;
import javax.money.format.MonetaryFormats;
import org.junit.Test;
public class MoneyTest {
@Test
public void testMoneyApi() {
MonetaryAmount eurAmount1 = Monetary.getDefaultAmountFactory().setNumber(1.1111).setCurrency("EUR").create();
MonetaryAmount eurAmount2 = Monetary.getDefaultAmountFactory().setNumber(1.1141).setCurrency("EUR").create();
MonetaryAmount eurAmount3 = eurAmount1.add(eurAmount2);
assertThat(eurAmount3.toString(), is("EUR 2.2252"));
MonetaryRounding defaultRounding = Monetary.getDefaultRounding();
MonetaryAmount eurAmount4 = eurAmount3.with(defaultRounding);
assertThat(eurAmount4.toString(), is("EUR 2.23"));
MonetaryAmountFormat germanFormat = MonetaryFormats.getAmountFormat(Locale.GERMAN);
assertThat(germanFormat.format(eurAmount4), is("EUR 2,23") );
}
}
Um tipo integral que representa o menor valor possível. Em outras palavras, seu programa deve pensar em centavos e não em dólares / euros.
Isso não deve impedir que a GUI traduza de volta para dólares / euros.
O BigDecimal pode ser usado, uma boa explicação de por que não usar Float ou Double pode ser vista aqui: Por que não usar Double ou Float para representar moeda?
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, que está 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 amostra da JSR 354: API Money e Currency:
Um exemplo de criação de um MonetaryAmount e impressão no console se parece com 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
Você deve usar o BigDecimal para representar valores monetários. Permite usar vários modos de arredondamento e, em aplicativos financeiros, o modo de arredondamento costuma ser um requisito difícil que pode até ser exigido por lei.
Eu usaria Joda Money
Ainda está na versão 0.6, mas parece muito promissor
Fiz uma marca de microbench (JMH) para comparar o Moneta (implementação JSR 354 da moeda java) com o BigDecimal em termos de desempenho.
Surpreendentemente, o desempenho do BigDecimal parece ser melhor que o do moneta. Eu usei a seguinte configuração moneta:
org.javamoney.moneta.Money.defaults.precision = 19 org.javamoney.moneta.Money.defaults.roundingMode = HALF_UP
package com.despegar.bookedia.money;
import org.javamoney.moneta.FastMoney;
import org.javamoney.moneta.Money;
import org.openjdk.jmh.annotations.*;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.concurrent.TimeUnit;
@Measurement(batchSize = 5000, iterations = 10, time = 2, timeUnit = TimeUnit.SECONDS)
@Warmup(iterations = 2)
@Threads(value = 1)
@Fork(value = 1)
@State(Scope.Benchmark)
@BenchmarkMode(Mode.Throughput)
public class BigDecimalBenchmark {
private static final Money MONEY_BASE = Money.of(1234567.3444, "EUR");
private static final Money MONEY_SUBSTRACT = Money.of(232323, "EUR");
private static final FastMoney FAST_MONEY_SUBSTRACT = FastMoney.of(232323, "EUR");
private static final FastMoney FAST_MONEY_BASE = FastMoney.of(1234567.3444, "EUR");
MathContext mc = new MathContext(10, RoundingMode.HALF_UP);
@Benchmark
public void bigdecimal_string() {
new BigDecimal("1234567.3444").subtract(new BigDecimal("232323")).multiply(new BigDecimal("3.4"), mc).divide(new BigDecimal("5.456"), mc);
}
@Benchmark
public void bigdecimal_valueOf() {
BigDecimal.valueOf(12345673444L, 4).subtract(BigDecimal.valueOf(232323L)).multiply(BigDecimal.valueOf(34, 1), mc).divide(BigDecimal.valueOf(5456, 3), mc);
}
@Benchmark
public void fastmoney() {
FastMoney.of(1234567.3444, "EUR").subtract(FastMoney.of(232323, "EUR")).multiply(3.4).divide(5.456);
}
@Benchmark
public void money() {
Money.of(1234567.3444, "EUR").subtract(Money.of(232323, "EUR")).multiply(3.4).divide(5.456);
}
@Benchmark
public void money_static(){
MONEY_BASE.subtract(MONEY_SUBSTRACT).multiply(3.4).divide(5.456);
}
@Benchmark
public void fastmoney_static() {
FAST_MONEY_BASE.subtract(FAST_MONEY_SUBSTRACT).multiply(3.4).divide(5.456);
}
}
Resultando em
Benchmark Mode Cnt Score Error Units
BigDecimalBenchmark.bigdecimal_string thrpt 10 479.465 ± 26.821 ops/s
BigDecimalBenchmark.bigdecimal_valueOf thrpt 10 1066.754 ± 40.997 ops/s
BigDecimalBenchmark.fastmoney thrpt 10 83.917 ± 4.612 ops/s
BigDecimalBenchmark.fastmoney_static thrpt 10 504.676 ± 21.642 ops/s
BigDecimalBenchmark.money thrpt 10 59.897 ± 3.061 ops/s
BigDecimalBenchmark.money_static thrpt 10 184.767 ± 7.017 ops/s
Sinta-se à vontade para me corrigir se estiver faltando alguma coisa
Para um caso simples (uma moeda) é suficiente Integer
/ Long
. Mantenha o dinheiro em centavos (...) ou centésimo / milésimo de centavos (qualquer precisão que você precisar com divisor fixo)
BigDecimal é o melhor tipo de dados a ser usado para a moeda.
Existem muitos contêineres para moeda, mas todos usam BigDecimal como o tipo de dados subjacente. Você não irá errar com o BigDecimal, provavelmente usando o BigDecimal.ROUND_HALF_EVEN.
Eu gosto de usar tipos minúsculos que envolvem um double, BigDecimal ou int, como as respostas anteriores sugeriram. (Eu usaria um duplo, a menos que surjam problemas de precisão).
Um tipo minúsculo oferece segurança de digitação, para que você não confunda dinheiro duplo com outras duplas.