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


183

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


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.


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

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.


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


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.



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


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)


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.


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.


6
Embora eu também goste de letras minúsculas, você nunca deve usar um duplo para armazenar um valor monetário.
— Orien
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.