3

Is a MongoDB's NumberLong really 64 bit signed integer really 64 bit?

MongoDB's NumberLong is said to be a 64 bit signed integer, which should mean we can play with -2^63 <= x <= 2^63-1, where x is a NumberLong. However, adding 1 or subtracting 1 from a NumberLong(x) does not return the expected value for x <= -2^54 or x >= 2^54, but correct values are returned for -2^53 <= x <= 2^53.
The reliable NumberLong numbers therefor seem to be 54 bit signed integers.

Why is this?
Am I doing someting wrong?

Sample from the mongo shell:

> NumberLong( Math.pow(2,54) )
NumberLong("18014398509481984")    // Expected
> NumberLong( Math.pow(2,54)-1 )
NumberLong("18014398509481984")    // **NOT** Expected
> NumberLong( -Math.pow(2,54) )
NumberLong("-18014398509481984")   // Expected
> NumberLong( -Math.pow(2,54)+1 )
NumberLong("-18014398509481984")   // **NOT** Expected

> NumberLong( Math.pow(2,53) )
NumberLong("9007199254740992")     // Expected
> NumberLong( Math.pow(2,53)-1 )
NumberLong("9007199254740991")     // Expected
> NumberLong( -Math.pow(2,53) )
NumberLong("-9007199254740992")    // Expected
> NumberLong( -Math.pow(2,53)+1 )
NumberLong("-9007199254740991")    // Expected

Using MongoDB 2.0.0

Community
  • 1
  • 1
fsto
  • 63
  • 1
  • 7

2 Answers2

2

Wow, this is puzzling. Clearly there is some sort of rounding error happening here.

> NumberLong("18014398509481984")-NumberLong("1");
18014398509481984

> NumberLong("18014398509481984")-NumberLong("2");
18014398509481982

> NumberLong("18014398509481984")+NumberLong("1");
18014398509481984

> NumberLong("18014398509481984")+NumberLong("2");
18014398509481984

> NumberLong("18014398509481984")+NumberLong("3");
18014398509481988

This is probably something wrong with the JavaScript engine that the shell runs in, rather than MongoDB itself. Check this out, for example--$inc works fine:

> db.test.insert({twoTo54:NumberLong("18014398509481984")});
> db.test.update({},{$inc:{twoTo54:NumberLong("1")}});
> db.test.find();
{ "_id" : ObjectId("4f204847756aa806028abce1"), "twoTo54" : NumberLong("18014398509481985") }
> db.test.update({},{$inc:{twoTo54:NumberLong("1")}});
> db.test.find();
{ "_id" : ObjectId("4f204847756aa806028abce1"), "twoTo54" : NumberLong("18014398509481986") }
> db.test.update({},{$inc:{twoTo54:NumberLong("1")}});
> db.test.find();
{ "_id" : ObjectId("4f204847756aa806028abce1"), "twoTo54" : NumberLong("18014398509481987") }

You have to be careful, though. If you use just a normal literal 1, it will convert the type to a Number, which then breaks the $inc:

> db.test.update({},{$inc:{twoTo54:1}});
> db.test.find();
{ "_id" : ObjectId("4f204847756aa806028abce1"), "twoTo54" : 18014398509481988 }
> db.test.update({},{$inc:{twoTo54:1}});
> db.test.find();
{ "_id" : ObjectId("4f204847756aa806028abce1"), "twoTo54" : 18014398509481988 }

And even if you go back to $inc with NumberLong("1"), it's still broken:

> db.test.update({},{$inc:{twoTo54:NumberLong("1")}});
> db.test.find();
{ "_id" : ObjectId("4f204847756aa806028abce1"), "twoTo54" : 18014398509481988 }

Definitely good to keep in mind.

Eve Freeman
  • 32,467
  • 4
  • 86
  • 101
0

Math.pow most likely operates on doubles. The integers are coerced to floats, and at 2^54, double precision numbers lose resolution at the 1's place. The information is already lost at the time you convert back to integer types.

do you get comparable results using left shift << or repeated multiplication?

SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
  • Your thought about the float mantissa would explain it all. Unfortunately left shifting integers only works for 32 bit signed and the repeated multiplication gave the same output as mentioned above. – fsto Oct 16 '11 at 18:54