[SERVER-15926] Add $round and/or $integer floating-point functions Created: 03/Nov/14  Updated: 31/May/19  Resolved: 14/Feb/19

Status: Closed
Project: Core Server
Component/s: Aggregation Framework
Affects Version/s: None
Fix Version/s: 4.1.9

Type: New Feature Priority: Major - P3
Reporter: Rod Adams Assignee: Patrick Meredith
Resolution: Done Votes: 12
Labels: aggregation, expression, neweng
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Backports
Depends
Documented
is documented by DOCS-12486 Docs for SERVER-15926: Add $round and... Closed
Backwards Compatibility: Fully Compatible
Backport Requested:
v4.0
Participants:

 Description   

Inclusion of an $int or $round operator, which can reliably group numbers which are semantically the same is thus desired.

Example:
In attempting to perform bucketing in an aggregation pipeline, where the buckets are defined as "each Xth of range Y", these functions are needed.

bucket = int( (x - min) / (max - min) * bucket_count )
// This relies on the int() function removing any real part of the number.
 
// At a semantic level, the following is the same:
bucket = (x - min - mod(x - min, (max - min) * bucket_count) / ((max - min) / bucket_count) 

Since this is all floating point math it is subject to errors such as 1.000000001 vs 0.999999999, and therefor less accurate.



 Comments   
Comment by Nicholas Zolnierz [ 31/May/19 ]

patrick.meredith asya What are your thoughts on backporting this? There was a subtle behavior change with $trunc and Infinity, where in 4.0 we would return NaN but in master we return Infinity. Spoke with Geert and the latter seems to be the desired behavior.

Comment by Githook User [ 14/Feb/19 ]

Author:

{'name': 'Patrick Meredith', 'email': 'pmeredit@gmail.com', 'username': 'pmeredit'}

Message: SERVER-15926: Implement $round function and allow a precision argument for $trunc

Closes #1297

Signed-off-by: Charlie Swanson <charlie.swanson@mongodb.com>
Branch: master
https://github.com/mongodb/mongo/commit/814b1f8237db9ecb27dd0ffa96f11edd58bac73e

Comment by Asya Kamsky [ 30/Jun/15 ]

I'm pretty sure my intention was to provide a "close enough" functionality to rounding to a specific number of decimal places - it's true that it hits this edge case at 0. Maybe I should change it to not accept rounding to 0 significant digits

Comment by Geert Bosch [ 30/Jun/15 ]

The round function, as you posted it, has a classical floating-point error:

> db.float.insert({x:0.49999999999999994})
WriteResult({ "nInserted" : 1 })
> db.float.find()
{ "_id" : ObjectId("5592094f6db020c8d6d1a7da"), "x" : 0.49999999999999994 }
> db.float.aggregate({$project:{xx:round("$x",0)}})
{ "_id" : ObjectId("5592094f6db020c8d6d1a7da"), "xx" : 1 }

The input value was less than 0.5, so why is it rounded to 1?

Answer: we should implement the $round function (with an optional argument indicating number of places, defaulting to 0) that also handles the edge cases (unlike adding 0.5 and truncating)

Comment by Asya Kamsky [ 03/Nov/14 ]

For completeness I also have a function that truncates:

truncate = function (val,places) {
     var p={ };
     var divider=Math.pow(10,places);
     p["$divide"]=[];
     var newval={"$multiply":[val,divider]};
     sub={"$subtract":[ newval, {"$mod":[newval, 1]} ]};
     p["$divide"].push(sub);
     p["$divide"].push(divider);
     return p;
}

Comment by Asya Kamsky [ 03/Nov/14 ]

I have a pre-defined function for this in the shell - similar technique could be used in other contexts until the proper feature is implemented:

round = function (val,places) {
     var p={ };
     var divider=Math.pow(10,places);
     p["$divide"]=[];
     var newval={$add:[{"$multiply":[val,divider]},.5]}
     sub={"$subtract":[ newval, {"$mod":[newval, 1]} ]};
     p["$divide"].push(sub);
     p["$divide"].push(divider);
     return p;
}

Given a value and number of digits to keep after decimal, it will round it.

Example:

> db.float.find()
{ "_id" : ObjectId("5457f12b89c37a1da2cba06c"), "x" : 1.05987 }
> db.float.aggregate({$project:{xx:round("$x",2)}})
{ "_id" : ObjectId("5457f12b89c37a1da2cba06c"), "xx" : 1.06 }

Generated at Thu Feb 08 03:39:25 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.