Eu herdei uma pilha enorme de código legado escrito em PHP em cima de um banco de dados MySQL. O que notei é que o aplicativo usa doubles
para armazenamento e manipulação de dados.
Agora me deparei com vários posts mencionando como double
não são adequados para operações monetárias por causa dos erros de arredondamento. No entanto, ainda não encontrei uma solução completa sobre como os valores monetários devem ser tratados no código PHP e armazenados em um banco de dados MySQL.
Existe uma prática recomendada quando se trata de lidar com dinheiro especificamente em PHP?
O que estou procurando são:
- Como os dados devem ser armazenados no banco de dados? tipo de coluna? Tamanho?
- Como os dados devem ser tratados em adição normal, subtração. multiplicação ou divisão?
- Quando devo arredondar os valores? Quanto arredondamento é aceitável, se houver?
- Existe uma diferença entre lidar com valores monetários grandes e valores baixos?
Nota: Um exemplo de código MUITO simplificado de como posso encontrar valores monetários na vida cotidiana (várias preocupações de segurança foram ignoradas para simplificação. É claro que na vida real eu nunca usaria meu código como este):
$a= $_POST['price_in_dollars']; //-->(ex: 25.06) will be read as a string should it be cast to double?
$b= $_POST['discount_rate'];//-->(ex: 0.35) value will always be less than 1
$valueToBeStored= $a * $b; //--> any hint here is welcomed
$valueFromDatabase= $row['price']; //--> price column in database could be double, decimal,...etc.
$priceToPrint=$valueFromDatabase * 0.25; //again cast needed or not?
Espero que você use esse código de exemplo como um meio de trazer mais casos de uso e não interpretá-lo literalmente, é claro.
Pergunta sobre bônus Se eu devo usar um ORM como Doctrine ou PROPEL, quão diferente será usar dinheiro no meu código.
decimal
tipo em C # tem precisão limitada, mas é perfeitamente adequado para valores monetários.Respostas:
Pode ser bastante complicado manipular números com PHP / MySQL. Se você usar decimal (10,2) e seu número for maior ou tiver maior precisão, ele será truncado sem erros (a menos que você defina o modo adequado para o servidor db).
Para manipular valores grandes ou valores de alta precisão, você pode usar uma biblioteca como o BCMath , permitindo que você faça operações básicas em grandes números e mantenha a precisão necessária.
Não tenho certeza do que exatamente você fará os cálculos, mas lembre-se de que (0,22 * 0,4576) + (0,78 * 0,4576) não será igual a 0,4576 se você não usar a precisão adequada durante o processo.
O tamanho máximo de DECIMAL no MySQL é 65, portanto deve ser mais que suficiente para qualquer finalidade. Se você usar o tipo de campo DECIMAL, ele será retornado como string, independentemente do uso de um ORM ou simplesmente do PDO / mysql (i).
DECIMAL com precisão que você precisa. Se você estiver usando taxas de câmbio, precisará de pelo menos quatro casas decimais
Use BCMath para salvar e por que usar float pode não ser uma boa ideia
Para valores monetários normais, duas casas decimais são aceitáveis, mas você pode precisar de mais se, por exemplo, estiver usando taxas de câmbio.
Depende do que você quer dizer com grande. Definitivamente, há uma diferença entre lidar com números com alta precisão.
fonte
Uma solução simples é armazená-los como números inteiros. 99,99 armazenado como 9999. Se isso não funcionar (e há muitos motivos pelos quais isso pode ser uma má escolha), você pode usar o tipo Decimal. http://dev.mysql.com/doc/refman/5.0/en/precision-math-decimal-changes.html no lado do mysql. No lado do php, encontrei este /programming/3244094/decimal-type-in-php que pode ser o que você procura.
Pergunta bônus: Difícil de dizer. O orm funcionará com base nos tipos de dados escolhidos. Eu diria que você poderia fazer algumas coisas com abstração para ajudar, mas esse problema específico não é resolvido simplesmente mudando para um ORM.
fonte
Vou tentar colocar minha experiência nisso:
Eu tenho usado
DECIMAL(10,2)
para o mysql sem problemas (8 completos e 2 decimais == 99.999.999,99 == grande quantidade), mas isso depende do intervalo de dinheiro que você precisará cobrir. Quantidades enormes devem ser tomadas com cuidado extra (valores máximos de flutuação do OS, por exemplo). Na parte decimal, uso 2 valores para evitar valores truncados ou arredondados. Tomando dinheiro, há poucos casos em que você precisa de mais casas decimais (nesse caso, você precisa garantir que o usuário trabalhe com todos eles, caso contrário, são dados inúteis)Trabalhe com uma moeda e uma tabela de câmbio (com datas). Dessa forma, você garante que sempre terá a quantidade correta salva. Um extra: salve valores completos e crie uma visualização com os resultados do cálculo. Isso ajudará você a fixar valores rapidamente
Novamente, depende do intervalo de dinheiro do seu sistema. Sempre pense nos termos do KISS, a menos que precise cair na bagunça da casa de câmbio
Dependendo do sistema operacional e das linguagens de programação, você sempre precisa verificar os valores máximo e mínimo
fonte
$valueToBeStored= $a * $b;
se$a
e$b
forem lidos como decimais no banco de dados, acho que eles serão convertidosdouble
no PHP, certo? isso afetará os números?$a
e$b
são retirados de db? portanto, no meu exemplo, você nunca precisa armazenar$valueToBeStored
porque sempre terá a fonte$a
e os$b
dados. Então você pode trabalhar programaticamente o valor em uma função ou mais ou criar uma visualização mysql com o resultado da coluna. Dessa forma, se qualquer valor tem que ser mudado você não precisa se preocupar sobre como modificar vários lugares (sujeito a erros)