Armazenando dinheiro em uma coluna decimal - qual precisão e escala?

173

Estou usando uma coluna decimal para armazenar valores monetários em um banco de dados e hoje estava pensando em qual precisão e escala usar.

Como colunas supostamente char de largura fixa são mais eficientes, eu estava pensando que o mesmo poderia ser verdade para colunas decimais. É isso?

E que precisão e escala devo usar? Eu estava pensando em precisão 24/8. Isso é um exagero, não é suficiente ou ok?


Isto é o que eu decidi fazer:

  • Armazene as taxas de conversão (quando aplicável) na própria tabela de transações, como um flutuador
  • Armazene a moeda na tabela da conta
  • O valor da transação será um DECIMAL(19,4)
  • Todos os cálculos usando uma taxa de conversão serão tratados pelo meu aplicativo, então eu mantenho o controle dos problemas de arredondamento

Eu não acho que um problema para a taxa de conversão seja um problema, pois é principalmente para referência, e eu o converteremos em decimal de qualquer maneira.

Obrigado a todos por sua contribuição valiosa.

Ivan
fonte
2
Faça a si mesmo a pergunta: É realmente necessário armazenar os dados em forma decimal? Não posso armazenar os dados como centavos / centavos -> inteiros?
Terence
5
DECIMAL(19, 4) é uma escolha popular. verifique isso também verifique aqui Formatos de Moeda Mundial para decidir quantas casas decimais usar, a esperança ajuda.
shaijut

Respostas:

181

Se você estiver procurando por um tamanho único, sugiro que DECIMAL(19, 4)seja uma escolha popular (um rápido Google confirma isso). Eu acho que isso se origina do antigo tipo de dados VBA / Access / Jet Currency, sendo o primeiro tipo de ponto fixo decimal no idioma; Decimalsó veio no estilo 'versão 1.0' (ou seja, não foi totalmente implementado) no VB6 / VBA6 / Jet 4.0.

A regra prática para armazenar valores decimais de ponto fixo é armazenar pelo menos mais uma casa decimal do que você realmente precisa para permitir o arredondamento. Uma das razões para mapear o Currencytipo antigo no front-end para DECIMAL(19, 4)digitar no back-end foi que Currencyexibia o arredondamento dos banqueiros por natureza, enquanto o DECIMAL(p, s)arredondamento por truncamento.

Uma casa decimal extra no armazenamento DECIMALpermite que um algoritmo de arredondamento personalizado seja implementado em vez de assumir o padrão do fornecedor (e o arredondamento dos banqueiros é alarmante, para dizer o mínimo, para um designer que espera que todos os valores que terminem em 0,5 para arredondar para zero) .

Sim, DECIMAL(24, 8)parece um exagero para mim. A maioria das moedas é cotada com quatro ou cinco casas decimais. Conheço situações em que é necessária uma escala decimal de 8 (ou mais), mas é aqui que uma quantia monetária 'normal' (digamos quatro casas decimais) foi proporcional, implicando que a precisão decimal deve ser reduzida adequadamente (considere também um tipo de ponto flutuante nessas circunstâncias). Hoje em dia, ninguém tem tanto dinheiro para exigir uma precisão decimal de 24 :)

No entanto, em vez de uma abordagem de tamanho único, algumas pesquisas podem estar em ordem. Pergunte ao seu designer ou especialista em domínio sobre regras contábeis que podem ser aplicáveis: GAAP, UE etc. Lembro-me vagamente de algumas transferências intra-estaduais da UE com regras explícitas para arredondar para cinco casas decimais, portanto, DECIMAL(p, 6)para armazenamento. Os contadores geralmente parecem favorecer quatro casas decimais.


PS Evite o MONEYtipo de dados do SQL Server, pois apresenta sérios problemas de precisão ao arredondar, entre outras considerações, como portabilidade, etc. Consulte o blog de Aaron Bertrand .


A Microsoft e os designers de idiomas escolheram o arredondamento de banqueiros porque os designers de hardware o escolheram [citação?]. Está consagrado nos padrões do Institute of Electrical and Electronics Engineers (IEEE), por exemplo. E os designers de hardware escolheram porque os matemáticos preferem. Veja Wikipedia ; Parafraseando: A edição de 1906 da Probabilidade e Teoria dos Erros chamou isso de 'regra do computador' ("computadores" significa seres humanos que realizam cálculos).

um dia quando
fonte
1
Veja esta resposta e esta página para saber mais sobre por que os designers de idiomas escolhem o arredondamento do Banker.
Nick Chammas
1
onedaywhen: O arredondamento do banqueiro não se deve à Microsoft. @NickChammas: nem é invenção de um designer de linguagem. A Microsoft e os designers de idiomas escolheram basicamente porque os designers de hardware escolheram; está consagrado nos padrões do Institute of Electrical and Electronics Engineers (IEEE), por exemplo. E os designers de hardware escolheram porque os matemáticos preferem. Veja en.wikipedia.org/wiki/Rounding#History ; Parafraseando: A edição de 1906 da Probabilidade e Teoria dos Erros chamou isso de 'regra do computador' ("computadores" significa seres humanos que realizam cálculos).
Phoog 28/03
9
então o que devo usar para o Bitcoin?
Toolkit
1
@onedaywhen por que é DECIMAL(19, 4)mais popular do que DECIMAL(19, 2)? A maioria das moedas mundiais tem apenas duas casas decimais.
Cokedude
3
@ zypA13510: sim, minha afirmação é tão dez anos atrás! Mas esta é a minha terceira mais votada para a resposta e responde por um pedaço justo de mudança no que diz respeito a minha SO representante isso estou quids em :)
onedaywhen
105

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:

  1. Converter valores em ponto flutuante
  2. Calcular novo valor
  3. 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 floorou 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!

Marcus Downing
fonte
1
Nunca diga nunca: às vezes, os montantes em dinheiro são proporcionalmente necessários e precisam somar novamente mais tarde. Exemplo: dividir o dividendo total (relativamente pequeno) dibido pelo número de ações em emissão (relativamente pequeno) para dar o lucro líquido por ação. Às vezes, as
flutuações
23
Eu mantenho meu nunca. A especificação de ponto flutuante possui imprecisões que adicionam mais cálculos que você faz. Se você precisar armazenar valores menores que um centavo ou centavo, defina o nível de precisão necessário e cumpra-o. Não use boia. Seriamente. É uma má idéia.
Marcus Downing
4
Esta resposta também está alinhada com as práticas recomendadas de javascript descritas por Douglas Crockford em sua "série Crockford on JavaScript", onde ele recomenda fazer todos os cálculos de moeda no PENNIES para evitar erros de máquina no arredondamento. Portanto, se você estiver trabalhando com moedas em javascript, faz muito sentido armazenar o valor dessa maneira.
paperreduction
1
Quando esses casos líquidos por ação, você pode somar os valores proporcionalmente, comparar com o total original e elaborar uma estratégia para lidar com o restante (ao usar tipos decimais / inteiros).
Shiv
2
Para o Mysql, eu recomendo armazenar o número inteiro (2500) como bigintse você pretender classificar pela quantidade. E não perca seu tempo com PHP 32bit ao lidar com grandes números inteiros, atualizar para 64 bits ou Node.js;)
Ricky Boyce
4

Ao manipular dinheiro no MySQL, use DECIMAL (13,2) se você souber a precisão de seus valores monetários ou use DOUBLE se você quiser apenas um valor aproximado suficientemente bom e rápido. Portanto, se seu aplicativo precisar lidar com valores monetários de até um trilhão de dólares (ou euros ou libras), isso deve funcionar:

DECIMAL(13, 2)

Ou, se você precisar cumprir o GAAP , use:

DECIMAL(13, 4)
pollux1er
fonte
3
Você pode vincular à parte específica das diretrizes GAAP em vez do conteúdo de um documento de 2500 páginas? Obrigado.
ReactingToAngularVues
@ReactingToAngularVues parece que a página mudou. Desculpe
pollux1er
2

Quatro casas decimais dariam a você a precisão de armazenar as menores subunidades monetárias do mundo. Você pode reduzi-lo ainda mais se precisar da precisão do micropagamento (nanopagamento ?!).

Eu também prefiro DECIMALtipos de dinheiro específicos ao DBMS, você é mais seguro mantendo esse tipo de lógica no aplicativo IMO. Outra abordagem na mesma linha é simplesmente usar um número inteiro [long], com a formatação em ¤unit.subunit para facilitar a leitura humana (¤ = símbolo de moeda) feita no nível do aplicativo.

bobince
fonte
1

O tipo de dados monetário no SQL Server possui quatro dígitos após o decimal.

Nos Manuais Online do SQL Server 2000:

Os dados monetários representam quantias positivas ou negativas de dinheiro. No Microsoft® SQL Server ™ 2000, os dados monetários são armazenados usando os tipos de dados money e smallmoney. Os dados monetários podem ser armazenados com precisão de quatro casas decimais. Use o tipo de dados monetários para armazenar valores no intervalo de -922.337.203.685.477,5808 a +922.337.203.685.477.5807 (requer 8 bytes para armazenar um valor). Use o tipo de dados smallmoney para armazenar valores no intervalo de -214.748.3648 a 214.748.3647 (requer 4 bytes para armazenar um valor). Se for necessário um número maior de casas decimais, use o tipo de dados decimal.

Austin Salonen
fonte
1

Às vezes, você precisará ir a menos de um centavo e há moedas internacionais que usam demoniações muito grandes. Por exemplo, você pode cobrar de seus clientes 0,088 centavos por transação. No meu banco de dados Oracle, as colunas são definidas como NUMBER (20,4)

WW.
fonte
1

Se você estiver realizando algum tipo de operação aritmética no banco de dados (multiplicando as taxas de cobrança e assim por diante), provavelmente precisará de muito mais precisão do que as pessoas sugerem, pelos mesmos motivos que nunca deseja usar algo menor que um valor de ponto flutuante de precisão dupla no código do aplicativo.

Hank Gay
fonte
Era o que eu estava pensando, mas em termos de taxas de câmbio (ou seja, converter dólares do Zimbawe em dólares americanos). Vou realizar algumas experiências nos bancos de dados que estou usando (psql, sqlite) para ver como eles lidam com o arredondamento em decimais muito pequenos.
227 Ivan
Além disso, os flutuadores não têm problemas de precisão em alguns dbms / idiomas?
227 Ivan
2
os carros alegóricos têm problemas de precisão em TODOS os idiomas.
Marcus Downing
A recomendação mais comum atualmente é usar precisão arbitrária (pensar BigDecimal), mas por um longo tempo foi de precisão dupla (pense em doublevez de float). Além disso, a precisão arbitrária possui penalidades significativas de desempenho em alguns casos. Testar é definitivamente a abordagem correta.
Hank Gay
0

Se você estivesse usando o IBM Informix Dynamic Server, teria um tipo MONEY que é uma variante secundária no tipo DECIMAL ou NUMERIC. É sempre um tipo de ponto fixo (enquanto DECIMAL pode ser um tipo de ponto flutuante). Você pode especificar uma escala de 1 a 32 e uma precisão de 0 a 32 (o padrão é uma escala de 16 e uma precisão de 2). Portanto, dependendo do que você precisa armazenar, você pode usar DECIMAL (16,2) - ainda grande o suficiente para manter o déficit federal dos EUA, até o centavo mais próximo - ou usar um intervalo menor ou mais casas decimais.

Jonathan Leffler
fonte
0

Eu acho que, em grande parte, os requisitos de seus clientes devem determinar qual precisão e escala usar. Por exemplo, no site de comércio eletrônico em que estou trabalhando que lida apenas com dinheiro em libras esterlinas, fui obrigado a mantê-lo em decimal (6, 2).

ayaz
fonte
0

Uma resposta tardia aqui, mas eu usei

DECIMAL(13,2)

que eu estou certo em pensar deve permitir até 99.999.999.999,99.

Mike Upjohn
fonte