Recentemente, implementamos um sistema que precisa lidar com valores em várias moedas e converter entre elas, e descobrimos algumas coisas da maneira mais difícil.
NUNCA USE NÚMEROS DE PONTO FLUTUANTE PARA DINHEIRO
A aritmética de ponto flutuante apresenta imprecisões que podem não ser notadas até que você estrague algo. Todos os valores devem ser armazenados como números inteiros ou decimais fixos, e se você optar por usar um tipo decimal fixo, certifique-se de entender exatamente o que esse tipo faz sob o capô (por exemplo, ele usa internamente um número inteiro ou ponto flutuante tipo).
Quando você precisa fazer cálculos ou conversões:
- Converter valores em ponto flutuante
- Calcular novo valor
- Arredonde o número e converta-o novamente em um número inteiro
Ao converter um número de ponto flutuante em um número inteiro na etapa 3, não basta convertê-lo - use uma função matemática para arredondá-lo primeiro. Isso geralmente será round
, embora em casos especiais possa ser floor
ou ceil
. Conheça a diferença e escolha com cuidado.
Armazene o tipo de um número ao lado do valor
Isso pode não ser tão importante para você se você estiver lidando apenas com uma moeda, mas foi importante para nós lidar com várias moedas. Usamos o código de três caracteres para uma moeda, como USD, GBP, JPY, EUR etc.
Dependendo da situação, também pode ser útil armazenar:
- Se o número é antes ou depois do imposto (e qual era a taxa do imposto)
- Se o número é o resultado de uma conversão (e de onde foi convertido)
Conheça os limites de precisão dos números com os quais você está lidando
Para valores reais, você quer ser tão preciso quanto a menor unidade da moeda. Isso significa que você não possui valores menores que um centavo, um centavo, um iene, um fen, etc. Não armazene valores com maior precisão do que isso sem motivo.
Internamente, você pode optar por lidar com valores menores; nesse caso, é um tipo diferente de valor da moeda . Verifique se o seu código sabe qual é qual e não os mistura. Evite usar valores de ponto flutuante mesmo aqui.
Adicionando todas essas regras, decidimos pelas seguintes regras. No código em execução, as moedas são armazenadas usando um número inteiro para a menor unidade.
class Currency {
String code; // eg "USD"
int value; // eg 2500
boolean converted;
}
class Price {
Currency grossValue;
Currency netValue;
Tax taxRate;
}
No banco de dados, os valores são armazenados como uma sequência no seguinte formato:
USD:2500
Isso armazena o valor de US $ 25,00. Conseguimos fazer isso apenas porque o código que lida com moedas não precisa estar dentro da camada do banco de dados, para que todos os valores possam ser convertidos em memória primeiro. Outras situações, sem dúvida, se prestam a outras soluções.
E caso não tenha deixado claro antes, não use float!