Overview
Las aplicaciones que manejan datos monetarios suelen requerir la capacidad de capturar unidades fraccionarias y emular el redondeo decimal con precisión al realizar operaciones aritméticas. La aritmética de punto flotante basada en binario utilizada por muchos sistemas modernos (es decir, float, double) no puede representar fracciones decimales exactas y requiere cierto grado de aproximación, lo que la hace inadecuada para la aritmética monetaria. Esta restricción es un factor importante a considerar al modelar datos monetarios.
Existen varios enfoques para modelar datos monetarios en MongoDB utilizando modelos numéricos y no numéricos.
Modelo numérico
El modelo numérico puede ser apropiado si necesita consultar la base de datos para encontrar coincidencias exactas y matemáticamente válidas o necesita realizar cálculos aritméticos del lado del servidor, por ejemplo, $inc, $mul y aritmética de canalización de agregación.
Los siguientes enfoques siguen el modelo numérico:
Utilizando el tipo BSON decimal, que es un formato de punto flotante basado en decimal capaz de proporcionar precisión exacta.
64
longUsar un 10 factor de escala para convertir el valor monetario a un entero de bits (tipo BSON) multiplicándolo por una potencia de factor de escala.
Non-Numeric Model
Si no es necesario realizar cálculos aritméticos del lado del servidor sobre datos monetarios o si las aproximaciones del lado del servidor son suficientes, puede ser adecuado modelar los datos monetarios utilizando el modelo no numérico.
El siguiente enfoque sigue el modelo no numérico:
Usando dos campos para el valor monetario: Un campo almacena el valor monetario exacto como un no numérico
stringy otro campo almacena una aproximación de punto flotante basada en binario (tipodoubleBSON) del valor.
Modelo numérico
Uso del tipo BSON decimal
El decimal128 tipo BSON utiliza el 754 decimal128 formato de numeración de punto flotante basado en decimales IEEE. A diferencia de los formatos de punto flotante basados en binario, como el double tipo BSON, decimal128 no aproxima valores decimales y proporciona la precisión necesaria para trabajar con datos monetarios.
En,mongosh decimal los valores se asignan y consultan mediante el Decimal128() constructor. El siguiente ejemplo añade un documento con precios de gasolina a una gasprices colección:
db.gasprices.insertOne( { "date" : ISODate(), "price" : Decimal128("2.099"), "station" : "Quikstop", "grade" : "regular" } )
La siguiente consulta coincide con el documento anterior:
db.gasprices.find( { price: Decimal128("2.099") } )
Para obtener más información sobre el decimal tipo, consulte Decimal.128
Convertir valores a decimal
Los valores de una colección se pueden transformar al tipo decimal realizando una transformación única o modificando la lógica de la aplicación para realizar la transformación a medida que accede a los registros.
Tip
Como alternativa al procedimiento descrito a continuación, a partir de la 4.0 versión, puede utilizar el $convert operador y su operador $toDecimal auxiliar para convertir valores Decimal128() a.
Transformación de colección una vez
Una colección se puede transformar iterando sobre todos los documentos de la colección, convirtiendo el valor monetario al tipo decimal y escribiendo el documento nuevamente en la colección.
Nota
Se recomienda encarecidamente agregar el valor decimal al documento como un nuevo campo y remover el campo antiguo más tarde, una vez que se hayan verificado los valores del nuevo campo.
Advertencia
Asegúrese de probar las conversiones decimal en un entorno de prueba aislado. Una vez creados o modificados los archivos de datos, estos dejarán de ser compatibles con versiones anteriores y no se puede degradar la versión de archivos de datos que contengan decimales.
Transformación del factor de escala:
Considere la siguiente colección que utilizó el enfoque del factor de escala y guardó el valor monetario como un 64entero de bits que representa la cantidad de centavos:
{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : Long("1999") }, { "_id" : 2, "description" : "Jeans", "size" : "36", "price" : Long("3999") }, { "_id" : 3, "description" : "Shorts", "size" : "32", "price" : Long("2999") }, { "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : Long("2495") }, { "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : Long("8000") }
El long valor se puede convertir a un decimal valor con el formato adecuado multiplicando price Decimal128("0.01") y con el $multiply operador. La siguiente secuencia de agregación asigna el valor convertido al nuevo priceDec campo en la $addFields etapa:
db.clothes.aggregate( [ { $match: { price: { $type: "long" }, priceDec: { $exists: 0 } } }, { $addFields: { priceDec: { $multiply: [ "$price", Decimal128( "0.01" ) ] } } } ] ).forEach( ( function( doc ) { db.clothes.save( doc ); } ) )
Los resultados del proceso de agregación se pueden verificar mediante la consulta db.clothes.find():
{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : Long(1999), "priceDec" : Decimal128("19.99") } { "_id" : 2, "description" : "Jeans", "size" : "36", "price" : Long(3999), "priceDec" : Decimal128("39.99") } { "_id" : 3, "description" : "Shorts", "size" : "32", "price" : Long(2999), "priceDec" : Decimal128("29.99") } { "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : Long(2495), "priceDec" : Decimal128("24.95") } { "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : Long(8000), "priceDec" : Decimal128("80.00") }
Si no desea agregar un nuevo campo con el decimal valor, puede sobrescribir el campo original. El siguiente método primero comprueba updateMany() que price existe y que es un;long luego, transforma el long valor en decimal y lo almacena en el price campo:
db.clothes.updateMany( { price: { $type: "long" } }, { $mul: { price: Decimal128( "0.01" ) } } )
Los resultados se pueden verificar utilizando la consulta db.clothes.find():
{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : Decimal128("19.99") } { "_id" : 2, "description" : "Jeans", "size" : "36", "price" : Decimal128("39.99") } { "_id" : 3, "description" : "Shorts", "size" : "32", "price" : Decimal128("29.99") } { "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : Decimal128("24.95") } { "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : Decimal128("80.00") }
Non-Numeric Transformation:
Considere la siguiente colección que utilizó el modelo no numérico y guardó el valor monetario como string con la representación exacta del 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" }
La siguiente función primero verifica que price exista y que sea un string, luego transforma el valor string en un valor decimal y lo almacena en el campo priceDec:
db.clothes.find( { $and : [ { price: { $exists: true } }, { price: { $type: "string" } } ] } ).forEach( function( doc ) { doc.priceDec = Decimal128( doc.price ); db.clothes.save( doc ); } );
La función no genera ningún resultado en la línea de comandos. Los resultados se pueden verificar mediante la consulta db.clothes.find():
{ "_id" : 1, "description" : "T-Shirt", "size" : "M", "price" : "19.99", "priceDec" : Decimal128("19.99") } { "_id" : 2, "description" : "Jeans", "size" : "36", "price" : "39.99", "priceDec" : Decimal128("39.99") } { "_id" : 3, "description" : "Shorts", "size" : "32", "price" : "29.99", "priceDec" : Decimal128("29.99") } { "_id" : 4, "description" : "Cool T-Shirt", "size" : "L", "price" : "24.95", "priceDec" : Decimal128("24.95") } { "_id" : 5, "description" : "Designer Jeans", "size" : "30", "price" : "80.00", "priceDec" : Decimal128("80.00") }
Transformación de la lógica de la aplicación
Es posible realizar la transformación al tipo decimal desde la lógica de la aplicación. En este caso, la aplicación se modificó para realizar la transformación al acceder a los registros.
La lógica de aplicación típica es la siguiente:
Pruebe que el nuevo campo existe y que es de tipo
decimalSi el nuevo campo
decimalno existe:Créelo convirtiendo correctamente los valores de campo antiguos
Eliminar el campo antiguo
Persistir el registro transformado
Usando un factor de escala
Nota
Se prefiere el uso del tipo decimal para modelar datos monetarios al método del factor de escala.
Para modelar datos monetarios utilizando el enfoque del factor de escala:
Determine la precisión máxima necesaria para el valor monetario. Por ejemplo, su aplicación podría requerir una precisión de hasta la décima de céntimo para valores monetarios en la moneda
USD.Convierta el valor monetario en un entero multiplicándolo por una potencia de 10 que garantice que la precisión máxima necesaria se convierta en el dígito menos significativo del entero. Por ejemplo, si la precisión máxima requerida es la décima de un centavo, multiplique el valor monetario por 1000.
Almacene el valor monetario convertido.
Por ejemplo, la siguiente escala 9.99 USD por 1000 para preservar la precisión hasta una décima de centavo.
{ price: 9990, currency: "USD" }
El modelo supone que para un valor monetario dado:
El factor de escala es consistente para una moneda; es decir, el mismo factor de escala para una moneda determinada.
El factor de escala es una propiedad constante y conocida de la moneda, es decir, las aplicaciones pueden determinar el factor de escala a partir de la moneda.
Al utilizar este modelo, las aplicaciones deben ser consistentes al realizar el escalamiento apropiado de los valores.
Para casos de uso de este modelo, consulte Modelo numérico.
Non-Numeric Model
Para modelar datos monetarios utilizando el modelo no numérico, almacene el valor en dos campos:
En un campo, codifique el valor monetario exacto como un tipo de datos no numérico; por ejemplo,
BinDataostring.En el segundo campo, almacene una aproximación de punto flotante de doble precisión del valor exacto.
El siguiente ejemplo utiliza el modelo no numérico para almacenar 9.99 USD para el precio y 0.25 USD para la tarifa:
{ price: { display: "9.99", approx: 9.9900000000000002, currency: "USD" }, fee: { display: "0.25", approx: 0.2499999999999999, currency: "USD" } }
Con cuidado, las aplicaciones pueden realizar consultas de rango y ordenación en el campo con la aproximación numérica. Sin embargo, el uso del campo de aproximación para las operaciones de consulta y ordenación requiere que las aplicaciones realicen un posprocesamiento del lado del cliente para decodificar la representación no numérica del valor exacto y, posteriormente, filtrar los documentos devueltos según el valor monetario exacto.
Para casos de uso de este modelo, consulte Modelo no numérico.