27

我正在使用 mongodb 聚合框架并进行一些计算,如下所示

db.RptAgg.aggregate( 
  {$group: {
    _id: {Region: "$RegionTxt", Mth: "$Month"},           
    ActSls: {$sum:"$ActSls"},
    PlnSls: {$sum:"$PlnSls"}
  }},
  {$project: {
    ActSls: 1,
    PlnSls: 1,
    ActToPln: {$cond: [
      {$ne: ["$PlnSls", 0]}, 
      {$multiply: [{$divide: ['$ActSls', '$PlnSls']}, 100]}, 
      0
    ]}
  }}
); 

我试图找出将结果四舍五入到小数点后 2 位的最佳和最简单的方法。以下是我的结果

{
  "result": [{
    "_id": {
      "Region": "East",
      "Mth": 201301
    },
    "ActSls": 72,
    "PlnSls": 102,
    "ActToPln": 70.58823529411765
  }],
  "ok": 1
}

我希望 "ActToPln" 在聚合框架本身的结果中显示 70.59而不是"ActToPln" : 70.58823529411765。我想避免在我的应用程序中进行舍入

你能帮忙吗?

以下是我使用的数据集。

{
    "_id" : ObjectId("51d67ef69557c507cb172572"),
    "RegionTxt" : "East",
    "Month" : 201301,
    "Date" : "2013-01-01",
    "ActSls" : 31,
    "PlnSls" : 51
}
{
    "_id" : ObjectId("51d67ef69557c507cb172573"),
    "RegionTxt" : "East",
    "Month" : 201301,
    "Date" : "2013-01-02",
    "ActSls" : 41,
    "PlnSls" : 51
}

提前致谢。南都

4

10 回答 10

19

没有$round运算符,但您可以在聚合框架中执行此操作 - 按特定顺序执行通常可以避免浮点精度问题。

> db.a.save({x:1.23456789})
> db.a.save({x:9.87654321})
> db.a.aggregate([{$project:{ _id:0, 
         y:{$divide:[
              {$subtract:[
                      {$multiply:['$x',100]},
                      {$mod:[{$multiply:['$x',100]}, 1]}
              ]},
              100]}
}}])
{ "y" : 1.23 }
{ "y" : 9.87 }

鉴于问题中的现有管道,请替换:

{$multiply:[{$divide: ['$ActSls', '$PlnSls']},100]}

{$divide:[
     {$subtract:[ 
          {$multiply:[
             {$divide: ['$ActSls','$PlnSls']},
             10000
          ]}, 
          {$mod:[
             {$multiply:[{$divide: ['$ActSls','$PlnSls']}, 10000 ]},
             1]}
          ]}, 
     100
]}

使用您的示例数据点,结果如下:

{ "ActSls" : 31, "PlnSls" : 51, "ActToPln" : 60.78 }
{ "ActSls" : 41, "PlnSls" : 51, "ActToPln" : 80.39 }
{ "ActSls" : 72, "PlnSls" : 102, "ActToPln" : 70.58 }
于 2014-03-22T12:33:51.197 回答
13

首先Mongo 4.2,有一个新的$round聚合运算符,可用于将具有特定精度的数字四舍五入到指定的小数

{ $round : [ <number>, <place> ] }

可以在聚合管道中使用它(这里我们x将 s 舍入到2小数位):

// db.collection.insert([{x: 1.23456}, {x: 9.87654}, {x: 0.055543}, {x: 12.345}])
db.collection.aggregate([{ $project: { "rounded_x": { $round: ["$x", 2] }}}])
// [{"rounded_x": 1.23}, {"rounded_x": 9.88}, {"rounded_x": 0.06}, {"rounded_x": 12.35}]

请注意,该place参数是可选的,省略它会导致四舍五入为整数(即​​四舍五入到小数点后 0 位)。

于 2019-03-14T19:11:39.303 回答
8

我不知道为什么,但所有答案(在此页面上)都给了我12.34. 12.345所以我写了自己的项目阶段:

x = 12.345

{'$project': {
    y: {'$divide': [{'$trunc': {'$add': [{'$multiply': ['$x', 100]}, 0.5]}}, 100]},
}},

它给12.35.


这是简单的算术,没有技巧:

  1. 12.345 * 100 = 1234.5 # 这一步将我们带到舍入位置:100 = 10^2(点后有两个符号)。Step 将被第 4 步平衡。
  2. 1234.5 + 0.5 = 1235.0 # 这里我得到了我的round half up
  3. truncate(1235.0) = 1235 # 简单地去掉小数部分
  4. 1235 / 100 = 12.35

但是,它不适用于负片(这对我的聚合来说已经足够了)。对于(正面和负面)两种情况,您都应该使用它abs

{'$project': {
    z: {'$multiply': [
        {'$divide': ['$x', {'$abs': '$x'}]}, 
        {'$divide': [{'$trunc': {'$add': [{'$multiply': [{'$abs': '$x'}, 100]}, 0.5]}}, 100]}
    ]},
}}

在这里,我得到数字的符号,用 abs 包装原始数字,然后通过舍入输出乘以符号。

于 2017-06-27T12:43:11.107 回答
5

mongo-round效果很好。我发现的最干净的方式。

说号码是3.3333333

var round = require('mongo-round');

db.myCollection.aggregate([
    { $project: {
        roundAmount: round('$amount', 2)  // it will become 3.33
    } }
]);
于 2016-08-25T04:26:24.093 回答
4

此解决方案正确地向上或向下舍入到 2dp:

"rounded" : {
  $subtract:[
    {$add:['$absolute',0.0049999999999999999]},
    {$mod:[{$add:['$absolute',0.0049999999999999999]}, 0.01]}
  ]
}

例如,它将 1.2499 向上舍入至 1.25,但将 1.2501 向下舍入至 1.25。

笔记:

  1. 该解决方案基于http://www.kamsky.org/stupid-tricks-with-mongodb/rounding-numbers-in-aggregation-framework中给出的示例
  2. 它解决了 Asya Kamsky 回答中的问题,即它只会截断并且不能正确向上/向下舍入;即使在评论中建议的更改之后。
  3. 加法因子中尾随 9 的数量很大,以适应高精度输入数字。根据要四舍五入的数字的精度,加法因子可能需要比这更精确。
于 2017-04-05T11:48:10.770 回答
2

当前版本的聚合框架中没有圆形运算符。你可以试试这个片段:

> db.a.save({x:1.23456789})
> db.a.save({x:9.87654321})
> db.a.aggregate([{$project:{y:{$subtract:['$x',{$mod:['$x', 0.01]}]}}}])
{
    "result" : [
        {
            "_id" : ObjectId("51d72eab32549f94da161448"),
            "y" : 1.23
        },
        {
            "_id" : ObjectId("51d72ebe32549f94da161449"),
            "y" : 9.870000000000001
        }
    ],
    "ok" : 1
}

但如您所见,由于精度问题,此解决方案效果不佳。在这种情况下,最简单的方法是听从@wiredprairie的建议并round在您的应用程序中制作 s。

于 2013-07-05T20:55:15.757 回答
1

让我说,很遗憾 MongoDB 缺少这个功能。我希望他们很快就会添加它。

但是,我想出了一个冗长的聚合管道。承认,它可能效率不高,但它尊重四舍五入的规则。

db.t.aggregate([{
    $project: {
        _id: 0,
        number: {
            $let: {
                vars: {
                    factor: {
                        $pow: [10, 3]
                    },
                },
                in: {
                    $let: {
                        vars: {
                            num: {$multiply: ["$$factor", "$number"]},
                        },
                        in: {
                            $switch: {
                                branches: [
                                    {case: {$gte: ["$$num", {$add: [{$floor: "$$num"}, 0.5]}]}, then: {$divide:[ {$add: [{$floor: "$$num"}, 1.0]},"$$factor"]}},
                                    {case: {$lt: ["$$num", {$add: [{$floor: "$$num"}, 0.5]}]}, then: {$divide:[{$floor: "$$num"}, "$$factor"]}}                                    
                                ]
                            }
                        }
                    }
                }
            }
        }
    }
}])

假设,我的集合中有以下名为t

{ number" : 2.341567 }
{ number" : 2.0012 }
{ number" : 2.0012223 }

运行上述查询后,我得到:

{ "number" : 2.342 }
{ "number" : 2.001 }
{ "number" : 2.001 }
于 2018-03-21T17:24:27.673 回答
1
rounded:{'$multiply': [{ "$cond": [{ "$gte": [ "$x", 0 ] }, 1,-1 ]},{'$divide': [{'$trunc': {'$add': [{'$multiply': [{'$abs': '$x'}, {$pow:[10,2]}]}, 0.5]}}, {$pow:[10,2]}]}]}

egvo 的解决方案很酷,但如果为零,则除以零。为避免 $cond 可用于检测符号

(将 x 替换为 field_name,将数字 2 替换为所需的十进制数)

于 2017-12-13T02:45:22.717 回答
0
{$divide:[
            {$cond: { if: { $gte: [ {$mod:[{$multiply:['$dollarAmount',100]}, 1]}, 0.5 ] }, then: {$add: [{$subtract:[
                  {$multiply:['$dollarAmount',100]},
                  {$mod:[{$multiply:['$dollarAmount',100]}, 1]}
          ]}
                ,1]}, else: {$subtract:[
                  {$multiply:['$dollarAmount',100]},
                  {$mod:[{$multiply:['$dollarAmount',100]}, 1]}
          ]} }}
          , 
          100]}

希望这些可以帮助完成。

于 2017-03-23T02:15:13.780 回答
0

注意$round聚合问题。使用double类型时有时会错误地舍入。这是一个例子:

5.885 --to double--> 5.8849999999999 ---$round(2)--> 5.88 (instead of 5.89)

这是一种解决方法:

> db.a.save({x:5.884})
> db.a.save({x:5.885})
> db.a.save({x:5.886})
> db.a.aggregate([{
     $project:{
         _id:0, 
         x: '$x',
         round: {$round: ['$x', 2]}
         better_round: {$round: [{$add: ['$x', 0.000000001]}, 2]}
     }
}])
Output:
{ "x" : 5.884, "round" : 5.88, "better_round" : 5.88 }
{ "x" : 5.885, "round" : 5.88, "better_round" : 5.89 }
{ "x" : 5.886, "round" : 5.89, "better_round" : 5.89 }
于 2022-02-09T11:45:48.243 回答