MongoDB 3.2 supports 3 numeric types: 32 bit integers, 64 bit integers, and 64 bit binary floating points. MongoDB 3.4 introduces a 4th type: 128 bit decimal floating point, also known as "decimal" or "Decimal128". The decimal type provides a workaround for the numerous fundamental issues inherent to using binary floating points to represent base 10 values.

What's Wrong With Binary Floating Points?

Here's a quick example of inserting a document into MongoDB with a property x with initial value 0.1, and then incrementing it by 0.2. The resulting value of x is not 0.3.

$ mongo test MongoDB shell version: 3.2.10 connecting to: test > > db.test.insertOne({ x: 0.1 }) { "acknowledged" : true, "insertedId" : ObjectId("588a3f711554cc7d70642fa1") } > db.test.updateOne({}, { $inc: { x: 0.2 } }) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 } > db.test.findOne() { "_id" : ObjectId("588a3f711554cc7d70642fa1"), "x" : 0.30000000000000004 } >

This problem is not limited to MongoDB, you get the same result in node:

$ node > 0.1 0.1 > 0.2 0.2 > 0.1 * 0.2 0.020000000000000004 > 0.1 * 0.1 0.010000000000000002 >

Silly JavaScript, can't even do math right. Let's try Python.

$ python Python 2.7.6 (default, Jun 22 2015, 17:58:13) [GCC 4.8.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> 0.1 0.1 >>> 0.2 0.2 >>> 0.1 + 0.2 0.30000000000000004

The fundamental issue is not with MongoDB, JavaScript, or Python, it's with how 0.1 is represented in binary: 0.00011001100110011... , a repeating decimal over 0011 . In other words, 0.1 is not representable by binary floating points. Nor are seemingly innocuous numbers like 1.126 and 1.789.

These accuracy problems make using floating points for monetary values cumbersome at best. Small errors add up fast, especially when you use floats to accumulate values over time like keeping track of the amount of fuel that should be in a tanker. In MongoDB 3.2 your only options were to either round values in the client or use a scale factor (converting to an integer for the purposes of arithmetic and then dividing again).

Using the decimal type, these accuracy issues are no longer a problem. The fundamental idea of the decimal type is that it represents numbers in base 10, rather than base 2. 0.1 is neatly representable using the hex string '01000000000000000000000000003e30'.

$ mongo test MongoDB shell version v3.4.1 connecting to: mongodb://127.0.0.1:27017/test > db.test.insertOne({ x: NumberDecimal('0.1') }) { "acknowledged" : true, "insertedId" : ObjectId("588a448a3eef9d93ffc9f197") } > db.test.updateOne({}, { $inc: { x: NumberDecimal('0.2') } }) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 } > db.test.findOne() { "_id" : ObjectId("588a448a3eef9d93ffc9f197"), "x" : NumberDecimal("0.3") } >

Mixing decimal floating points and binary floating points is likely a bad idea, but MongoDB seems to handle that too:

> db.test.insertOne({ x: NumberDecimal('0.1') }) { "acknowledged" : true, "insertedId" : ObjectId("588a4a708cefd826806dbe19") } > db.test.updateOne({}, { $inc: { x: 0.2 } }) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 } > db.test.findOne() { "_id" : ObjectId("588a4a708cefd826806dbe19"), "x" : NumberDecimal("0.300000000000000") } >

> db.test.insertOne({ x: 0.1 }) { "acknowledged" : true, "insertedId" : ObjectId("588a4a94c215d53cf9d8f3d5") } > db.test.updateOne({}, { $inc: { x: NumberDecimal('0.2') } }) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 } > db.test.findOne() { "_id" : ObjectId("588a4a94c215d53cf9d8f3d5"), "x" : NumberDecimal("0.300000000000000") } >

Decimal in Node.js

JavaScript has no native support for decimal floating points, so working with the decimal type has some troublesome edge cases. Here's a rudimentary example of using the decimal type in Node.js.

Keep in mind that the decimal type is new in version 2.2.0 of the MongoDB Node.js driver, so make sure you use mongodb >= 2.2.0 .

const mongodb = require ( 'mongodb' ); let db; mongodb.MongoClient.connect( 'mongodb://localhost:27017/test' ). then(_db => { db = _db; return db.collection( 'test' ).insertOne({ x: mongodb.Decimal128.fromString( '0.1' ) }); }). then(() => db.collection( 'test' ).updateOne({}, { $inc: { x: mongodb.Decimal128.fromString( '0.2' ) } })). then(() => db.collection( 'test' ).findOne()). then(res => console .log( JSON .stringify(res, null , ' ' ))). catch (error => console .error( 'error' , error));

The output of this script looks like this:

$ node decimal.js { "_id": "588a4d181a468447d61bd118", "x": { "$numberDecimal": "0.3" } }

The key detail you should notice is that the JSON output is in MongoDB extended JSON format (the $numberDecimal property) and the decimal is represented as a string rather than a number. Switching back and forth between extended JSON and the actual Decimal type is easy with the mongodb-extended-json npm package.

As of this writing, the Decimal type in Node.js does not have any arithmetic helpers.

> Decimal128.fromString('0.1').add(Decimal128.fromString('0.2')) TypeError: Decimal128.fromString(...).add is not a function at repl:1:30

To do arithmetic in Node.js, you would need to convert the decimal's string representation to a native JavaScript number. You can also get into the nitty-gritty of manipulating the buffer underlying the MongoDB driver's decimal type but I wouldn't recommend it. In most simple cases, the decimal string representation should be easy to convert to a number using parseFloat() .

Mongoose 4.8.0 also has support for the decimal type. Mongoose's type casting really shines here, it automatically converts strings into decimals for you:

const mongoose = require ( 'mongoose' ); mongoose.connect( 'mongodb://localhost:27017/test' ); var Doc = mongoose.model( 'Test' , new mongoose.Schema({ x: mongoose.Schema.Types.Decimal })); Doc.create({ x: '0.1' }). then(doc => doc.update({ $inc: { x: '0.2' } }).then(() => doc)). then(doc => Doc.findById(doc)). then(doc => console .log( 'doc' , doc.toObject())). catch (error => console .error(error)); doc { _id: 588 a5a0b9621524d3d84a059, x: { '$numberDecimal' : '0.3' }, __v: 0 }

Moving On