Que tipo de dados usar para dinheiro em Java? [fechadas]

183

Que tipo de dados você deve usar para obter dinheiro em Java?

questborn
fonte
2
Depende de quais operações você fará. Por favor, ofereça mais informações.
Eversor
@eversor Você pode me dar uma descrição de que tipo de dados deve ser usado para diferentes operações?
questborn
1
Estou fazendo cálculos que exigem que eu represente com precisão centavos.
Questmand
Você é capaz de prever a maior quantia de dinheiro que seu aplicativo precisará gerenciar? E, seus cálculos, eles serão simples (adições etc.) ou operações financeiras mais complexas?
Eversor

Respostas:

133

Java possui uma Currencyclasse 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.

Buhake Sindi
fonte
5
Por que não podemos usar float ou double?
Erran Morad
20
@Borat Sagdiyev Esta é a razão pela qual . Além disso, você pode consultar isso .
Buhake Sindi
2
@Borat: você pode, se souber o que está fazendo, consulte este artigo de Peter Lawrey. mas parece pelo menos um aborrecimento tão grande fazer todo o arredondamento quanto usar o BigDecimals.
22915 Nathan Hughes
35
"Se eu tivesse um centavo para cada vez que eu vi alguém uso FLOAT para armazenar moeda, eu tenho R $ 999.997634" - Bill Karwin
Collin Krawll
36

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-apicomo 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") );
    }
}
Abdull
fonte
E a serialização e o salvamento no db? Que formato deve ser usado para enviar por cabo?
Paweł Szczur 07/12/2015
1
Acredito que a Oracle dedicou novamente incluindo o Java Money no Java 9. Realmente uma vergonha. Mas ótima resposta. Nós ainda pode usá-lo com Maven
borjab
3
Você tem uma fonte para a Oracle decidir incluir o Java Money no Java 9?
Abdull
25

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.

catraca arrepiante
fonte
Tenha em mente que a quantidade de dinheiro pode transbordar o tamanho do int
Eversor
5
@eversor que precisaria de mais de 20 milhões de dólares a maioria dos aplicativos não precisam que muito se eles fazem uma longa será suficiente como nem mesmo os nossos govenrments lidar com mone suficiente para transbordar que
catraca aberração
4
@ratchetfreak Provavelmente melhor usar um longo tempo então.
Trognanders
5
Muitos bancos lidam com somas muito maiores que US $ 20.000.000 por dia. Isso nem leva em conta moedas como o iene, com grandes taxas de câmbio para o dólar. Os tipos inteiros podem ser melhores para evitar problemas de arredondamento, embora fiquem confusos com os cálculos de juros e taxas de câmbio. No entanto, dependendo do aplicativo, você pode precisar de um tipo inteiro de 64 bits.
Alchymist
Idealmente, os microdólares, na verdade, como se você fizer, por exemplo, US $ 10/3, o erro de arredondamento (3333,3 => 3333,0) não afeta tanto o valor final (neste caso, não afeta o valor real, embora seja perigoso assumir que isso nunca acontecerá). Isso é especialmente importante se você estiver fazendo muitos cálculos seguidos antes que o usuário veja o resultado, pois os erros de arredondamento serão compostos.
Chris Browne
11

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:

  1. Uma API para lidar com, por exemplo, valores monetários e moedas
  2. APIs para suportar implementações intercambiáveis
  3. Fábricas para criar instâncias das classes de implementação
  4. Funcionalidade para cálculos, conversão e formatação de valores monetários
  5. API Java para trabalhar com Money and Currencies, que está planejada para ser incluída no Java 9.
  6. 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

Affy
fonte
Tudo isso é bom, mas como Federico sugerido acima, parece mais lento do que BigDecimal :-)) piada de mau gosto, em seguida, apenas, mas vou dar-lhe testar agora um ano mais tarde ...
kensai
6

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.

Sandeep Pathak
fonte
6

Eu usaria Joda Money

Ainda está na versão 0.6, mas parece muito promissor

Liviu T.
fonte
6

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

Federico Gaule Palombarani
fonte
Interessante, eu vou correr mesmo teste com o mais recente material em JDK9
kensai
4

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)

Grigory Kislin
fonte
3

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.

Anthony Blake
fonte
2

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.

Garrett Smith
fonte
6
Embora eu também goste de letras minúsculas, você nunca deve usar um duplo para armazenar um valor monetário.
Orien