Em um jogo simples de simulação de negócios (construído em Java + Slick2D), a quantia atual de dinheiro de um jogador deve ser armazenada como um float
ou um int
, ou algo mais?
No meu caso de uso, a maioria das transações usará centavos (US $ 0,50, US $ 1,20, etc.), e cálculos simples de taxa de juros serão envolvidos.
Vi pessoas dizendo que você nunca deveria usar float
para moeda, bem como pessoas dizendo que você nunca deveria usar int
para moeda. Sinto que devo usar int
e arredondar todos os cálculos de porcentagem necessários. O que devo usar?
Currency
tipo como o Delphi, que usa matemática em ponto fixo escalado para fornecer matemática decimal sem os problemas de precisão inerentes ao ponto flutuante?BigDecimal
para esse tipo de problemas.Respostas:
Você pode usar
int
e considerar tudo em centavos. $ 1,20 são apenas 120 centavos. Na exibição, você coloca o decimal no lugar a que pertence.Os cálculos de juros seriam truncados ou arredondados. assim
Dessa forma, você não tem decimais confusos sempre aparecendo. Você pode ficar rico adicionando o dinheiro não contabilizado (devido a arredondamentos) em sua própria conta bancária
fonte
round
não há motivo real para rejeitarint
, existe?Ok, eu vou pular.
Meu conselho: é um jogo. Acalme-se e use-o
double
.Aqui está o meu raciocínio:
float
tem um problema de precisão que aparece ao adicionar unidades a milhões, portanto, embora possa ser benigno, eu evitaria esse tipo.double
só começa a ter problemas em torno dos quintilhões (um bilhão de bilhões).int
elong
inútil, porque você não sabe onde parar com os decimais.BigDecimal
fornecerá precisão arbitrária, mas ficará dolorosamente lento, a menos que você a prenda em algum momento. Quais são as regras de arredondamento? Como você escolhe onde parar? Vale a pena ter mais bits de precisão do quedouble
? Eu acredito que nao.A razão pela qual a aritmética de ponto fixo é usada em aplicações financeiras é porque elas são determinísticas. As regras de arredondamento são perfeitamente definidas, às vezes por lei, e devem ser rigorosamente aplicadas, mas o arredondamento ainda ocorre em algum momento. Qualquer argumento a favor de um determinado tipo com base na precisão é provavelmente falso. Todos os tipos têm problemas de precisão com o tipo de cálculo que você fará.
Exemplos práticos
Vejo alguns comentários afirmando coisas sobre arredondamento ou precisão com as quais discordo. Aqui estão alguns exemplos adicionais para ilustrar o que quero dizer.
Armazenamento : se sua unidade base for o centavo, você provavelmente desejará arredondar para o centavo mais próximo ao armazenar valores:
Você não terá absolutamente nenhum problema de arredondamento ao adotar esse método que também não teria com um tipo inteiro.
Recuperando : todos os cálculos podem ser feitos diretamente nos valores duplos, sem conversões:
Observe que o código acima não funcionará se você substituir
double
porint64_t
: haverá uma conversão implícita em edouble
, em seguida, truncamento emint64_t
, com uma possível perda de informações.data.GetValue ()Comparando : comparações são a única coisa a se acertar com tipos de ponto flutuante. Sugiro usar um método de comparação como este:
Justiça de arredondamento
Suponha que você tenha US $ 9,99 na conta com juros de 4%. Quanto o jogador deve ganhar? Com arredondamentos inteiros, você recebe $ 0,03; com o arredondamento de ponto flutuante, você recebe US $ 0,04. Eu acredito que o último é mais justo.
fonte
int_64t
s.10/3 = 3+3+4
ou10/3 = 3+3+3 (+1)
. Para todas as outras operações, números inteiros funcionam perfeitamente, diferentemente do ponto flutuante, que pode obter problemas de arredondamento em qualquer operação.Os tipos de ponto flutuante em Java (
float
,double
) não são uma boa representação para moedas devido a um motivo principal - há um erro de máquina no arredondamento. Mesmo que um cálculo simples retorne um número inteiro, como12.0/2
(6.0), o ponto flutuante pode arredondá-lo incorretamente (devido à representação específica desses tipos na memória) como6.0000000000001
ou5.999999999999998
ou semelhante. Isso é resultado do arredondamento específico da máquina que ocorre no processador e é exclusivo do computador que o calculou. Geralmente, raramente é um problema operar com esses valores, já que o erro é bastante negligente, mas é difícil mostrar isso para o usuário.Uma solução possível para isso seria usar implementações personalizadas do tipo de dados de ponto flutuante, como
BigDecimal
. Ele suporta melhores mecanismos de cálculo que, pelo menos, isolam os erros de arredondamento para não serem específicos da máquina, mas são mais lentos em termos de desempenho.Se você precisar de alta produtividade, é melhor seguir os tipos simples. Caso você opere com dados financeiros importantes e cada centavo seja importante (como um aplicativo de Forex ou algum jogo de cassino), recomendo que você use
Long
oulong
.Long
permitiria lidar com grandes quantidades e boa precisão. Suponha que você precise, digamos, 4 dígitos após o ponto decimal, tudo o que você precisa é multiplicar a quantia por 10000. Tendo experiência no desenvolvimento de jogos de cassino on-line, já viLong
ser frequentemente usado para representar o dinheiro em centavos. . Em aplicações Forex, a precisão é mais importante, portanto, você precisará de um multiplicador maior - ainda assim, números inteiros estão livres de problemas de arredondamento de máquinas (é claro que o arredondamento manual, como em 3/2, você deve cuidar de si mesmo).Uma opção aceitável seria usar os tipos de ponto flutuante padrão -
Float
eDouble
, se o desempenho for mais importante que a precisão, até centésimos de centavo. Então, na sua lógica de exibição, tudo o que você precisa é usar uma formatação predefinida , para que a feiura de um possível arredondamento da máquina não chegue ao usuário.fonte
Para jogos de pequena escala e onde a velocidade do processo, a memória é um problema importante (devido à precisão ou ao trabalho com o co-processador matemático pode tornar-se dolorosamente lento), há o dobro .
Mas para jogos de grande escala (por exemplo, jogos sociais) e onde a velocidade do processo, a memória não é limitada, o BigDecimal é melhor. Porque aqui
Recursos:
De https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency
De Bloch, J., Effective Java, 2ª ed. Item 48:
Também dê uma olhada
fonte
Você quer armazenar sua moeda em
long
e calcular a sua moeda emdouble
, pelo menos, como um backup. Você deseja que todas as transações ocorram comolong
.O motivo pelo qual você deseja armazenar sua moeda
long
é que não deseja perder nenhuma moeda.Vamos supor que você use a
double
e não tenha dinheiro. Alguém te dá três centavos e depois os leva de volta.Bem, isso não é tão legal. Talvez alguém com US $ 10 queira doar sua fortuna dando-lhe três centavos e depois US $ 9,70 a outra pessoa.
E então você devolve os centavos:
Isso está quebrado.
Agora, vamos usar um longo e acompanharemos décimos de centavos (então 1 = $ 0,001). Vamos dar a todos no planeta um bilhão, cento e doze milhões, setenta e cinco mil, cento e quarenta e três dólares:
Espere, podemos dar a todos mais de um bilhão de dólares e gastar apenas um pouco mais de dois? O estouro é um desastre aqui.
Assim, sempre que você está calculando uma quantia de dinheiro para transferir, utilização
double
eMath.round
para obter umlong
. Em seguida, corrija os saldos (adicione e subtraia ambas as contas) usandolong
.Sua economia não vai vazar e chegará a um bilhão de dólares.
Existem problemas mais complicados - por exemplo, o que você faz se fizer vinte pagamentos? * - mas isso deve ajudá-lo a começar.
* Você calcula o que é um pagamento, arredondado para
long
; depois multiplique20.0
e verifique se está dentro do alcance; Nesse caso, você multiplica o pagamento20L
para obter o valor deduzido do seu saldo. Em geral, todas as transações devem ser tratadas comolong
, portanto, você realmente precisa resumir todas as transações individuais; você pode multiplicar como um atalho, mas não se esqueça de adicionar erros de arredondamento e não estourar, o que significa que você precisa verificardouble
antes de fazer o cálculo reallong
.fonte
Eu diria que qualquer valor que possa ser exibido ao usuário quase sempre deve ser um número inteiro. O dinheiro é apenas o exemplo mais importante disso. Causar 225 de dano quatro vezes a um monstro com 900 HP e descobrir que ele ainda tem 1 HP restante subtrairá da experiência tanto quanto descobrir que você é uma fração invisível de um centavo, sem fornecer algo.
No lado mais técnico, acho que vale a pena notar que não é preciso voltar aos carros alegóricos para fazer coisas avançadas, como o interesse. Desde que você tenha espaço suficiente no tipo inteiro escolhido, uma multiplicação e uma divisão substituirão uma multiplicação por um número decimal, por exemplo, para adicionar 4%, arredondado para baixo:
Para adicionar 4%, arredondado por convenções padrão:
Aqui não há imprecisão do ponto de flutuação, o arredondamento sempre se divide exatamente na
.5
marca.Edite, a verdadeira questão:
Vendo como o debate tem ido eu começo a pensar que descrevendo o que a pergunta é tudo pode ser mais útil do que um simples
int
/float
resposta. No cerne da questão, não se trata de tipos de dados, mas de controlar os detalhes de um programa.Usar um número inteiro para representar um valor não inteiro força o programador a lidar com os detalhes da implementação. "Que precisão usar?" e "Que maneira de arredondar?" são perguntas que devem ser respondidas explicitamente.
Um float, por outro lado, não força o programador a se preocupar, ele já faz praticamente o que seria de esperar. Mas como um flutuador não é uma precisão infinita, ocorrerão alguns arredondamentos, e esse arredondamento é bastante imprevisível.
O que acontece em um uso flutua e deseja assumir o controle do arredondamento? Acontece que é quase impossível. A única maneira de tornar um float verdadeiramente previsível é usar apenas valores que possam ser representados em
2^n
s inteiros . Mas essa construção torna os carros alegóricos bastante difíceis de usar.Portanto, a resposta para a pergunta simples é: Se você deseja assumir o controle, use números inteiros; caso contrário, use flutuadores.
Mas a pergunta que está sendo debatida é apenas outra forma de pergunta: você quer assumir o controle?
fonte
Mesmo que seja "apenas um jogo", eu usaria o Money Pattern de Martin Fowler, apoiado por um longo tempo.
Por quê?
Localização (L10n): Usando esse padrão, você pode localizar facilmente a moeda do seu jogo. Pense nos jogos antigos de magnatas como "Transport Tycoon". Eles permitem facilmente que o jogador mude a moeda do jogo (ou seja, da libra esterlina para o dólar americano) para atender à moeda do mundo real.
E
Isso significa que você pode armazenar 9.000 vezes o suprimento de dinheiro M2 dos EUA atual (~ 10.000 bilhões de dólares). Dando-lhe espaço suficiente para usar qualquer outra moeda mundial, provavelmente, mesmo aqueles que tiveram / têm hiperinflação (se curioso, veja a inflação alemã pós-Segunda Guerra Mundial , onde 1 libra de pão era 3.000.000.000 de marcos)
Longo é fácil de persistir, muito rápido e deve fornecer espaço suficiente para todos os cálculos de juros usando apenas aritmética inteira, a resposta do eBusiness fornece uma explicação sobre como fazer isso.
fonte
Quanto trabalho você deseja dedicar a isso? Quão importante é a precisão? Você se preocupa em rastrear os erros fracionários que ocorrem com o arredondamento e a imprecisão de representar números decimais em um sistema binário?
Por fim, tenho a tendência de gastar um pouco mais de tempo codificando e implementando testes de unidade para "casos de canto" e casos problemáticos conhecidos - então, eu estaria pensando em termos de um BigInteger modificado que engloba quantidades arbitrariamente grandes e mantém os bits fracionários com um BigDecimal ou uma parte do BigRational (dois BigIntegers, um para o denominador e outro para o numerador) - e inclua o código para manter a parte fracionária em uma fração real, adicionando (talvez apenas periodicamente) qualquer parte não fracionária ao BigInteger principal. Então, eu controlava internamente tudo em centavos apenas para manter as partes fracionárias fora dos cálculos da GUI.
Provavelmente maneira complexa para um jogo (simples) - mas bom para uma biblioteca publicada como código aberto! Só precisaria descobrir maneiras de manter o bom desempenho ao lidar com os bits fracionários ...
fonte
Certamente não flutuar. Com apenas 7 dígitos, você só tem precisão como:
12.345,67 123.456,7x
Veja bem, já com 10 ^ 5 dólares, você está perdendo a noção dos centavos. double é utilizável para fins de jogos, não na vida real devido a preocupações de precisão.
Mas longo (e com isso quero dizer 64 bits ou muito tempo em C ++) não é suficiente se você estiver rastreando transações e resumindo-as. É o suficiente para manter as participações líquidas de qualquer empresa, mas todas as transações de um ano podem transbordar. Isso depende do tamanho do seu "mundo" financeiro no jogo.
fonte
Outra solução que adicionarei aqui é fazer uma classe de dinheiro. A classe seria algo como (poderia simplesmente ser uma estrutura).
Isso permitiria representar os centavos nesse caso como um número inteiro e os dólares inteiros como um número inteiro. Como você tem um sinalizador separado por ser negativo, você pode usar números inteiros não assinados para seus valores, o que duplica a quantidade possível (você também pode escrever uma classe numérica que aplique essa idéia aos números para obter números REALMENTE grandes). Tudo o que você precisa fazer é sobrecarregar os operadores matemáticos e você pode usá-lo como qualquer tipo de dados antigo. Consome mais memória, mas realmente expande os limites de valor.
Isso poderia ser expandido ainda mais para incluir itens como número de unidades parciais por unidades inteiras, para que você possa ter moedas que se subdividem em algo que não seja 100 subunidades, neste caso, centavos por dólar. Você poderia ter 125 floopies para formar um flooper, por exemplo.
Editar:
Expandirei a ideia de que você também pode ter uma espécie de tabela de consulta, que a classe monetária pode acessar que forneceria uma taxa de câmbio para sua moeda versus todas as outras moedas. Você pode incorporar isso nas funções de sobrecarga do operador. Portanto, se você tentar adicionar automaticamente 4 USD a 4 libras esterlinas, isso fornecerá automaticamente 6,6 libras (no momento da escrita).
fonte
Conforme mencionado em um dos comentários sobre sua pergunta original, esse problema foi abordado no StackExchange: https://stackoverflow.com/questions/8148684/what-is-the-best-data-type-to-use-for- dinheiro-em-java-app
Basicamente, os tipos de ponto flutuante nunca devem ser usados para representar moedas e, como a maioria das linguagens, o Java tem um tipo mais apropriado. A própria documentação da Oracle sobre primitivas sugere o uso do BigDecimal .
fonte
Use classe , é o melhor caminho. Você pode ocultar a implementação do seu dinheiro, pode alternar o dobro / o que quiser a qualquer momento.
Exemplo
No seu código, use simplesmente getter, é a melhor maneira que eu penso e você pode armazenar métodos mais úteis.
fonte