Representando Valores Monetários em Java [fechado]

94

Eu entendo que BigDecimal é a melhor prática recomendada para representar valores monetários em Java. O que você usa? Existe uma biblioteca melhor que você prefere usar?

dshaw
fonte
4
dê uma olhada no JSR 354
yegor256
1
Aqui está uma classe de moeda que você pode copiar e estender: java-articles.info/articles/?p=254
Gilbert Le Blanc
Consulte também a implementação de referência de JSR-354 github.com/JavaMoney/jsr354-ri
derrota em

Respostas:

81

BigDecimaltodo o caminho. Já ouvi falar de algumas pessoas criando suas próprias classes Cashou Moneyque encapsulam um valor em dinheiro com a moeda, mas sob a pele ainda é umBigDecimal , provavelmente com BigDecimal.ROUND_HALF_EVENarredondamento.

Edit: Como Don menciona em sua resposta , existem projetos de código aberto como tempo e dinheiro , e embora eu os aplauda por tentar evitar que os desenvolvedores tenham que reinventar a roda, eu simplesmente não tenho confiança suficiente em uma biblioteca pré-alfa para usar em um ambiente de produção. Além disso, se você vasculhar sob o capô, verá que eles BigDecimaltambém usam .

nove lados
fonte
4
+1. Decidimos adicionar uma classe de contêiner que também consome uma moeda. Isso é útil ao renderizar valores monetários em tabelas.
Daniel Hiller
1
sim, essa é uma abordagem bastante comum e faz muito sentido. Uma advertência para isso é quando você tem que lidar com o iene japonês, já que ele não tem uma denominação de moeda menor como centavos, então precisa de suas próprias regras de arredondamento.
noves lados
3
@ninesided dá um ótimo exemplo de por que lançar o seu próprio é uma resposta ruim. "Oh, e por falar nisso, não funciona para $ CURRENCY_X." É um bom sinal de que também não funciona com muitas outras moedas.
James Moore
1
@JamesMoore Eu discordo que "rolar seu próprio" é uma má abordagem, você só precisa estar ciente das possíveis limitações da abordagem que você escolher, daí a razão para eu mencioná-la. É trivial implementar regras de arredondamento diferentes por moeda, mas se o seu sistema só precisa negociar em dólares ou euros, você não precisa fazer engenharia excessiva.
noves lados de
1
Dê uma olhada em stackoverflow.com/questions/5134237/… por apenas uma razão pela qual BigDecimal é um problema. A contabilidade em todo o planeta é apenas um pântano de casos especiais, e tentar varrê-los todos para debaixo do tapete do BigDecimal simplesmente não funciona.
James Moore
52

Pode ser útil para as pessoas que chegam aqui pelos mecanismos de pesquisa saberem sobre o JodaMoney: http://www.joda.org/joda-money/ .

Iñaki Ibarrola Atxa
fonte
Obrigado. Venho querendo acrescentar uma observação sobre Joda Money. Você já usou?
dshaw
2
+1 parece interessante, fico feliz em ver que está BigDecimalsob o capô!
noves lados de
8

Uma biblioteca conveniente que encontrei anteriormente é a biblioteca Joda-Money . Uma de suas implementações é de fato baseada em BigDecimal. É baseado no ISO-4217 especificação para moedas e pode suportar uma lista de moedas personalizada (carregada via CVS).

Esta biblioteca possui um pequeno número de arquivos que podem ser acessados ​​rapidamente se forem necessárias modificações. Joda-Money é publicado sob a licença Apache 2.0.

Bashar
fonte
7

Se você estiver usando apenas dólares e centavos, eu usaria um longo (compensado por 2 casas decimais). Se você precisar de mais detalhes, o grande número decimal pode ser o melhor.

De qualquer forma, provavelmente estenderia a classe para ter um .toString () que usa o formato correto e como um lugar para colocar outros métodos que possam surgir (por um longo tempo, a multiplicação e a divisão serão incorretas se o decimal for 't ajustado)

Além disso, se você usar definir sua própria classe e interface, poderá substituir a implementação à vontade.

Bill K
fonte
2
Cuidado, mesmo comprado pode ser muito curto para manter a dívida federal dos EUA em centavos ... se não agora, em alguns anos.
Ingo
3
Eu concordo - qualquer grande quantidade de dólares (ou talvez se você estiver rastreando dinheiro em ienes) você deveria usar BigDecimal - mas mesmo assim eu consideraria seriamente usar uma classe de contêiner para isso. Acho que a maior parte da complexidade da programação vem de pessoas que não definem classes pequenas e simples em torno de coleções e tipos intrínsecos.
Bill K
3

BigDecimal ou outra representação de ponto fixo é o que geralmente é necessário para dinheiro.

As representações e cálculos de ponto flutuante ( Double, Float) são inexatos, levando a resultados errôneos.

Ken Gentil
fonte
7
Estritamente falando, BigDecimal também é inexato; apenas corresponde melhor com o arredondamento decimal com o qual estamos acostumados na vida diária e permite que você especifique os modos de arredondamento.
Michael Borgwardt
1
@Michael Borgwardt BigDecimal difere do IEEE FP porque uma escala explícita é especificada. Embora nem todas as operações sejam exatas, isso garante que um conjunto de operações e comportamento seja sempre exato e a escala seja constante, enquanto a escala para IEEE FP diminui com o valor.
1
O que isso tem a ver com dinheiro? Organizações de contabilidade em todo o mundo geralmente têm requisitos muito específicos sobre como você faz matemática em sua moeda. BigDecimal corresponde precisamente a cada um desses padrões? Isso acontecerá no próximo ano, quando esses padrões mudarem? E BigDecimal nem chega perto de especificar regras de arredondamento úteis para moedas.
James Moore
2

Você tem que ter muito cuidado ao lidar com tempo e dinheiro.

Quando você está trabalhando com dinheiro, espero que todos saibam que nunca deve usar float ou double.

Mas não tenho certeza sobre BigDecimal.

Na maioria dos casos, você ficará bem se apenas manter o controle dos centavos em um int ou longo. Desta forma, você nunca lida com uma casa decimal.

Você só exibe dólares quando o imprime. Sempre trabalhe com centavos internos usando números inteiros. Isso pode ser complicado se for necessário dividir ou usar Math.abs ().

No entanto, você pode se importar com meio centavo, ou mesmo um centésimo de centavo. Não sei qual é a boa maneira de fazer isso. Você só precisa lidar com um milésimo de centavos e usar um longo. Ou talvez você seja forçado a usar BigDecimal

Eu faria muito mais leitura sobre isso, mas ignore todo mundo que começa a falar sobre usar um float ou dobre para representar dinheiro. Eles estão apenas procurando problemas.

Sinto que meu conselho não está completo, então, por favor, pense mais nele. Você está lidando com tipos perigosos!

Pirolístico
fonte
2
Por que você precisaria ser "forçado" a usar BigDecimal? Sobre o que você está inseguro? É claramente superior a trabalhar com centavos, pois permite especificar explicitamente os modos de arredondamento.
Michael Borgwardt
1
@MichaelBorgwardt: sim, ele permite que você especifique um pequeno subconjunto dos modos de arredondamento necessários para moedas. Assim? (Dica: o arredondamento de moedas é decidido, geralmente, por organizações de contabilidade nacional. Eles ficam perfeitamente satisfeitos em lançar em casos especiais estranhos. Consulte stackoverflow.com/questions/5134237/… para apenas uma das muitas razões divertidas pelas quais o arredondamento BigDecimal é completamente inútil aqui.)
James Moore
@James: Como exatamente isso é "inútil"? Como implementar esses casos especiais seria mais difícil com BigDecimal do que com outra coisa?
Michael Borgwardt
1
OK, completamente inútil é muito forte. Na classe complicada que abstrai a moeda, as regras de arredondamento de BigDecimal são provavelmente úteis em algumas instâncias específicas para construir um subconjunto das maneiras pelas quais o arredondamento de moeda acontece. Mas o caso geral é que as regras de arredondamento para moedas requerem mecanismos que estão sujeitos a mudanças ao longo do tempo (uma vez que as agências de contabilidade humana elaboram as regras e são livres para alterá-las). A questão não é sobre euros (ou o que quer que substitua os euros no próximo mês ...), ou dólares em 2011, é sobre moeda, então você tem que lidar com muita complexidade desagradável.
James Moore
2

Criar uma classe Money é o caminho a percorrer. Usando BigDecimal (ou mesmo um int) por baixo. Em seguida, usando a classe Currency para definir a convenção de arredondamento.

Infelizmente, sem sobrecarregar o operador, o Java torna bastante desagradável a criação desses tipos básicos.

Kozyarchuk
fonte
2

Existe uma biblioteca melhor, tempo e dinheiro . IMO, é muito superior às bibliotecas fornecidas pelo JDK para representar esses 2 conceitos.

Dónal
fonte
3
Esta resposta foi postada três anos atrás. Hoje, o projeto de tempo e dinheiro ainda é pré-alfa de acordo com esse link.
James Moore
1
@JamesMoore Boa chamada. A resposta agora tem 7 anos e o projeto ainda não é estável.
Navin,
1

Definitivamente não é BigDecimal. Existem tantas regras especiais para arredondamento e apresentação que você precisa se preocupar.

Martin Fowler recomenda a implementação de uma classe Money dedicada para representar valores de moeda e que também implementa regras para conversão de moeda.

lindelof
fonte
6
e o tipo de dados subjacente de sua classe Money? BigDecimal.
Novidade
1
Isso não é verdade. Você poderia usar Integer na classe de dinheiro, que é o que Martin faz. Já fiz isso muitas vezes.
egervari
A recomendação está correta; cálculos envolvendo dinheiro são um vasto pântano de casos especiais que mudam com o tempo. BigDecimal pode ser útil como uma pequena parte da solução, mas certamente não é geral.
James Moore
1

Ei, aqui está um artigo muito interessante sobre BigDecimal, e um exemplo ilustrativo de por que às vezes é usado em vez de duplos. Tutorial BigDecimal .

arg20
fonte
0

Você pode usar a classe DecimalFormat ao exibir um valor de moeda. Ele fornece suporte à localização e é bastante extensível.


fonte
0

Eu encapsularia BigDecimal na classe Money, que também tem uma moeda, como alguém mencionado acima. O importante é que você faça uma quantidade extrema de testes de unidade e principalmente se estiver trabalhando com moedas diferentes. Também é uma boa ideia se você adicionar um construtor conveniente que pega uma string ou um método de fábrica que faz o mesmo, para que você possa escrever seus testes da seguinte forma:

   assertEquals(Money.create("100.0 USD").add("10 GBP"),Money.create("116 USD"));
Per Arneng
fonte
0

Sempre há restrições e especificidades envolvidas. Qualquer pessoa sem experiência suficiente para avaliar as questões sutis descritas no artigo a seguir deve reconsiderar seriamente antes de lidar com dados financeiros do mundo real:

http://lemnik.wordpress.com/2011/03/25/bigdecimal-and-your-money

BigDecimal dificilmente é a única representação correta ou a única peça do quebra-cabeça. Dadas certas condições, usar uma classe Money apoiada por centavos armazenados como um inteiro pode ser suficiente e seria muito mais rápido do que BigDecimal. Sim, isso implica o uso de dólares como moeda e limites de valores, mas essas restrições são perfeitamente aceitáveis ​​para muitos casos de uso e todas as moedas têm casos especiais para arredondamento e sub-denominações de qualquer maneira, portanto, não há uma solução "universal".

Craig
fonte
1
Este parece ser um comentário em outra postagem em vez de uma resposta real. Também é excessivamente vitriólico. Por favor, tente ser mais civilizado no futuro.
Slater Victoroff