JavaScript handling of NumberDecimal data

I was wondering about researching monetary values with the sample data. First I did a test with my local mongod. I am using db.version() 4.0.0. In the shell I run these lines

use qa
db.nums.insertOne( {name: 'John', val1: NumberDecimal("0.1"), val2: NumberDecimal("0.2") } );
db.nums.insertOne( {name: 'Jane', val1: 0.1, val2: 0.2 } );

Then the query

db.nums.find().pretty();
{
        "_id" : ObjectId("5c4761f796df99b45ed013f5"),
        "name" : "John",
        "val1" : NumberDecimal("0.1"),
        "val2" : NumberDecimal("0.2")
}
{
        "_id" : ObjectId("5c4763c796df99b45ed013f6"),
        "name" : "Jane",
        "val1" : 0.1,
        "val2" : 0.2
}

In node.js I then run

var MongoClient = require('mongodb').MongoClient;
async function connectToServer(whenDone)
{
	var url = 'mongodb://127.0.0.1:27017/qa';
	try {
		var client = await MongoClient.connect(url, {useNewUrlParser: true });
		var db = client.db('qa');
		var coll = db.collection('nums');
		var cursor = coll.find({});
		var data = await cursor.toArray();
		console.log('data', data);
		}
	catch (ex)
		{
		console.log(ex);
		}
}
connectToServer( );

But the output is

data [ 
  { _id: 5c4761f796df99b45ed013f5, name: 'John', '': 0 },
  { _id: 5c4763c796df99b45ed013f6,
    name: 'Jane',
    val1: 0.1,
    val2: 0.2 } ]

What’s up with the first record’s values not showing up?
Thank you.
Bill

I think you’re most likely using an outdated driver. Make sure you installed and are requiring the latest driver. You can check your package-lock.json to see which version is installed. This worked for me using 3.1.6 mongodb nodejs driver.

Also looks like NumberDecimal returns a buffer, so be aware that you will have to convert it to a string using toString() method.

console.log('Converted to string:', data[0].val1.toString());

My console output:

data [ { _id: 5c4769b48ae2636c000974ab,
    name: 'John',
    val1:
     Decimal128 {
       _bsontype: 'Decimal128',
       bytes: <Buffer 01 00 00 00 00 00 00 00 00 00 00 00 00 00 3e 30> },
    val2:
     Decimal128 {
       _bsontype: 'Decimal128',
       bytes: <Buffer 02 00 00 00 00 00 00 00 00 00 00 00 00 00 3e 30> } },
  { _id: 5c4769b48ae2636c000974ac,
    name: 'Jane',
    val1: 0.1,
    val2: 0.2 } ]
Converted to string: 0.1

Thank you for the version reference. Indeed it was a version problem. Running

npm show mongodb version

showed

2.1.21

So then I ran,

 npm install mongodb --save

The updated node test file is

var MongoClient = require('mongodb').MongoClient;
async function connectToServer(whenDone)
{
	var url = 'mongodb://127.0.0.1:27017/qa';
	try {
		var client = await MongoClient.connect(url, {useNewUrlParser: true });
		var db = client.db('qa');
		var coll = db.collection('nums');
		var cursor = coll.find({});
		var data = await cursor.toArray();
		console.log('data', data);
		for (var doc of data)
			{
			var sampleValue1 = doc.val1;
			var sampleValue2 = doc.val2;
			console.log();
			console.log( sampleValue1, typeof sampleValue1, sampleValue1.constructor.name);
			var addResult = sampleValue1 + sampleValue2;
			console.log('addResult', addResult);
			}
		}
	catch (ex)
		{
		console.log(ex);
		}
}
connectToServer( );

I now see the same value objects as you with the buffers etc. Of concern is the simple addition statement

var addResult = sampleValue1 + sampleValue2;

which shows

Decimal128 {
  _bsontype: 'Decimal128',
  bytes: <Buffer 01 00 00 00 00 00 00 00 00 00 00 00 00 00 3e 30> } 'object' 'Decimal128'
addResult 0.10.2   <--------- Not expected

0.1 'number' 'Number'
addResult 0.30000000000000004  <--- Expected due to binary float representations :-(

The NumberDecimal class seems to lack all mathematical operation support!
https://mongodb.github.io/node-mongodb-native/3.0/api/Decimal128.html

I am interested in what approaches developers can take to perform NumberDecimal math operations inside node.js (which for whatever reason are inappropriate to be done via aggregation framework operations)?

Many thanks,

You can absolutely do the math operations inside mongodb aggeggation pipeline. I think that’s the whole point.

For exmaple using your data:

db.nums.insertOne( {name: 'John', val1: NumberDecimal("0.1"), val2: NumberDecimal("0.2") } );
db.nums.insertOne( {name: 'Jane', val1: 0.1, val2: 0.2 } );

Since John has a NumberDecimal 128bit instead of a Double, if you run the following projection that does multiplicatoin on the two numbers you’ll see a difference.


var pipeline = [
  {
    '$project': {
      'name': 1, 
      'total': {
        '$multiply': [
          '$val1', '$val2'
        ]
      }
    }
  }
];

db.qa.aggregate(pipeline);
{ "_id" : ObjectId("5c4769b48ae2636c000974ab"), "name" : "John", "total" : NumberDecimal("0.02") }
{ "_id" : ObjectId("5c4769b48ae2636c000974ac"), "name" : "Jane", "total" : 0.020000000000000004 }

You can see that the NumberDecimal values multiplied correctly while the Double type (64bit) multiplied incorrectly.

pawlowsg, I am sorry for the confusion. Yes, as you point out the aggregation world supports decimal math without loss of precision. Thank you for the pipeline example as well.

To clarify I meant some node,js JavaScript logic that did a computation that we did not want to have done, or could not have done for some reason, within the aggregation pipeline.

Thanks to all for their suggestions.

1 Like

I see, there are multiple ways of dealing with floating point numbers in the computer world. Depending on what you’re planning to do and how percise you need your math to be. It will depend on what kind of application your creating. For example when it comes to saving money in a database, most developers choose to save the value as cents rather than floating point numbers.

In any case,

Your question is not mongoDB specific but rather you should look into how javascript handles floating point numbers. I suggest you look into the Math.js library as it can help you with precision arithmetic.

1 Like