Dados monetários do modelo
Nesta página
Visão geral
Aplicativos que lidam com dados monetários geralmente exigem a capacidade de capturar unidades fracionárias de moeda e precisam emular arredondamentos decimais com precisão exata ao executar aritmética. A aritmética de ponto flutuante baseada em binários usada por muitos sistemas modernos (i.e., flutuante, double) é incapaz de representar frações decimais exatas e requer algum grau de aproximação, tornando-a inadequada para a aritmética monetária. Essa restrição é uma consideração importante ao modelar dados monetários.
Existem várias abordagens para modelar dados monetários no MongoDB usando os modelos numéricos e não numéricos.
Modelo numérico
O modelo numérico pode ser apropriado se você precisar queryr o banco de dados para obter correspondências exatas e matematicamente válidas ou se precisar executar aritmética no lado do servidor, por exemplo, $inc
, $mul
e aritmética de pipeline de agregação.
As seguintes abordagens seguem o modelo numérico:
Usando o tipo Decimal BSON , que é um formato de ponto flutuante baseado em decimal capaz de fornecer precisão exata.
Usando um fator de escala para converter o valor monetário em um inteiro de 64bits (
long
tipo BSON) multiplicando por uma potência de 10 fator de escala.
Modelo não numérico
Se não houver necessidade de executar aritmética no lado do servidor em dados monetários, ou se as aproximações no lado do servidor forem suficientes, a modelagem de dados monetários usando o modelo não numérico pode ser adequada.
A seguinte abordagem segue o modelo não numérico:
Utilizando dois campos para o valor monetário: um campo armazena o valor monetário exato como um
string
não numérico e outro campo armazena uma aproximação de ponto flutuante baseada em binário (tipo JSONdouble
) do valor.
Modelo numérico
Usando o tipo BSON decimal
O tipo decimal128
BSON usa o formato de numeração de ponto flutuante baseado em decimal IEEE 754 decimal128
. Ao contrário dos formatos de ponto flutuante baseados em binário, como o tipo double
BSON, o decimal128
não aproxima os valores decimais e é capaz de fornecer a precisão exata necessária para trabalhar com dados monetários.
No mongosh
, os valores decimal
são atribuídos e querydos usando o construtor Decimal128()
. O exemplo a seguir adiciona um documento que contém preços de gás a uma coleção gasprices
:
db.gasprices.insertOne( { "date" : ISODate(), "price" : Decimal128("2.099"), "station" : "Quikstop", "grade" : "regular" } )
A seguinte consulta corresponde ao documento acima:
db.gasprices.find( { price: Decimal128("2.099") } )
Para obter mais informações sobre o tipo decimal
, consulte Decimal128.
Convertendo Valores em Decimais
Os valores de uma coleção podem ser transformados para o tipo decimal
executando uma transformação única ou modificando a lógica do aplicativo para executar a transformação à medida que acessa os registros.
Dica
Alternativa ao procedimento descrito abaixo, a partir da versão 4.0, você pode usar o $convert
e seu operador de $toDecimal
auxiliar para converter valores em Decimal128()
.
Transformação de coleção única
Uma coleção pode ser transformada iterando todos os documentos na coleção, convertendo o valor monetário para o tipo decimal
e gravando o documento de volta na coleção.
Observação
É altamente recomendável adicionar o valor decimal
ao documento como um novo campo e remover o campo antigo posteriormente, quando os valores do novo campo tiverem sido verificados.
Aviso
Certifique-se de testar conversões decimal
em um ambiente de teste isolado. Depois que os arquivos de dados forem criados ou modificados, não serão mais compatíveis com versões anteriores e não haverá suporte para downgrade de arquivos de dados contendo decimais.
Transformação do fator de escala:
Considere a seguinte coleção que usou a abordagem fator de escala e salvou o valor monetário como um número inteiro de 64 bits representando o número de centavos:
{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : NumberLong("1999") }, { "_id" : 2, "description" : "Jeans", "size" : "36", "price" : NumberLong("3999") }, { "_id" : 3, "description" : "Shorts", "size" : "32", "price" : NumberLong("2999") }, { "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : NumberLong("2495") }, { "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : NumberLong("8000") }
O valor long
pode ser convertido em um valor decimal
formatado adequadamente, multiplicando price
e NumberDecimal("0.01")
usando o operador $multiply
. O seguinte pipeline de agregação atribui o valor convertido ao novo campo priceDec
na etapa $addFields
:
db.clothes.aggregate( [ { $match: { price: { $type: "long" }, priceDec: { $exists: 0 } } }, { $addFields: { priceDec: { $multiply: [ "$price", NumberDecimal( "0.01" ) ] } } } ] ).forEach( ( function( doc ) { db.clothes.save( doc ); } ) )
Os resultados do pipeline de agregação podem ser verificados utilizando a consulta db.clothes.find()
:
{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : NumberLong(1999), "priceDec" : NumberDecimal("19.99") } { "_id" : 2, "description" : "Jeans", "size" : "36", "price" : NumberLong(3999), "priceDec" : NumberDecimal("39.99") } { "_id" : 3, "description" : "Shorts", "size" : "32", "price" : NumberLong(2999), "priceDec" : NumberDecimal("29.99") } { "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : NumberLong(2495), "priceDec" : NumberDecimal("24.95") } { "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : NumberLong(8000), "priceDec" : NumberDecimal("80.00") }
Se você não desejar adicionar um novo campo com o valor decimal
, o campo original poderá ser substituído. O método updateMany()
a seguir primeiro verifica se price
existe e se é um long
e, em seguida, transforma o valor long
em decimal
e o armazena no campo price
:
db.clothes.updateMany( { price: { $type: "long" } }, { $mul: { price: NumberDecimal( "0.01" ) } } )
Os resultados podem ser verificados utilizando a consulta db.clothes.find()
:
{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : NumberDecimal("19.99") } { "_id" : 2, "description" : "Jeans", "size" : "36", "price" : NumberDecimal("39.99") } { "_id" : 3, "description" : "Shorts", "size" : "32", "price" : NumberDecimal("29.99") } { "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : NumberDecimal("24.95") } { "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : NumberDecimal("80.00") }
Transformação não numérica:
Considere a seguinte coleção que utilizou o modelo não numérico e salvou o valor monetário como um string
com a representação exata do valor:
{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : "19.99" } { "_id" : 2, "description" : "Jeans", "size" : "36", "price" : "39.99" } { "_id" : 3, "description" : "Shorts", "size" : "32", "price" : "29.99" } { "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : "24.95" } { "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : "80.00" }
A função a seguir verifica primeiro se price
existe e se é um string
, depois transforma o valor string
em um valor decimal
e o armazena no campo priceDec
:
db.clothes.find( { $and : [ { price: { $exists: true } }, { price: { $type: "string" } } ] } ).forEach( function( doc ) { doc.priceDec = NumberDecimal( doc.price ); db.clothes.save( doc ); } );
A função não envia nada para a linha de comando. Os resultados podem ser verificados usando a consulta db.clothes.find()
:
{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : "19.99", "priceDec" : NumberDecimal("19.99") } { "_id" : 2, "description" : "Jeans", "size" : "36", "price" : "39.99", "priceDec" : NumberDecimal("39.99") } { "_id" : 3, "description" : "Shorts", "size" : "32", "price" : "29.99", "priceDec" : NumberDecimal("29.99") } { "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : "24.95", "priceDec" : NumberDecimal("24.95") } { "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : "80.00", "priceDec" : NumberDecimal("80.00") }
Transformação da lógica de aplicativos
É possível executar a transformação para o tipo decimal
a partir da lógica de aplicativos. Neste cenário, o aplicativo foi modificado para realizar a transformação à medida que acessa os registros.
A lógica típica do aplicativo é a seguinte:
Teste se o novo campo existe e se é do tipo
decimal
Se o novo campo
decimal
não existir:Crie-o convertendo corretamente os valores dos campos antigos
Remover o campo antigo
Persistir o registro transformado
Usando um fator de escala
Observação
É preferível usar o tipo decimal para modelar dados monetários em relação ao método Scale Factor .
Para modelar dados monetários usando a abordagem do fator de escala:
Determine a precisão máxima necessária para o valor monetário. Por exemplo, sua aplicação pode exigir precisão até o décimo de um centavo para valores monetários na moeda
USD
.Converta o valor monetário em um número inteiro multiplicando o valor por uma potência de 10 que garante que a precisão máxima necessária se torne o dígito menos significativo do número inteiro. Por exemplo, se a precisão máxima exigida for o décimo de um centavo, multiplique o valor monetário por 1000.
Armazene o valor monetário convertido.
Por exemplo, as seguintes escalas 9.99 USD
por 1000 para preservar a precisão até um décimo de um centavo.
{ price: 9990, currency: "USD" }
O modelo pressupõe que para um determinado valor monetário:
O fator de escala é consistente para uma moeda; ou seja, o mesmo fator de escala para uma determinada moeda.
O fator de escala é uma propriedade constante e conhecida da moeda; ou seja, os aplicativos podem determinar o fator de escala a partir da moeda.
Ao usar esse modelo, os aplicativos devem ser consistentes na execução da escala apropriada dos valores.
Para casos de uso deste modelo, consulte Modelo numérico.
Modelo não numérico
Para modelar dados monetários usando o modelo não numérico, armazene o valor em dois campos:
Em um campo, codifique o valor monetário exato como um tipo de dados não numéricos; por exemplo,
BinData
oustring
.No segundo campo, armazene uma aproximação de ponto flutuante de precisão dupla do valor exato.
O exemplo a seguir usa o modelo não numérico para armazenar 9.99 USD
pelo preço e 0.25 USD
pela taxa:
{ price: { display: "9.99", approx: 9.9900000000000002, currency: "USD" }, fee: { display: "0.25", approx: 0.2499999999999999, currency: "USD" } }
Com alguns cuidados, os aplicativos podem realizar consultas de intervalo e classificação no campo com a aproximação numérica. No entanto, o uso do campo de aproximação para as operações de consulta e classificação requer que os aplicativos executem pós-processamento do lado do cliente para decodificar a representação não numérica do valor exato e, em seguida, filtrar os documentos retornados com base no valor monetário exato.
Para casos de uso deste modelo, consulte Modelo não numérico.