【问题标题】:Rounding to 2 decimal places using MongoDB aggregation framework使用 MongoDB 聚合框架四舍五入到小数点后 2 位
【发布时间】:2013-07-03 04:29:45
【问题描述】:

我正在使用 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
}

提前致谢。 南都

【问题讨论】:

  • 您应该提供一些示例或数据并简化您的问题
  • 我已经更新了问题并提供了我使用的数据集。谢谢amezhenin。
  • 为什么不在客户端做呢?在聚合框架中没有一种自然的方法可以做到这一点。您需要使用 map reduce 来执行舍入功能。

标签: mongodb aggregation-framework rounding


【解决方案1】:

注意$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 }

【讨论】:

    【解决方案2】:

    我不知道为什么,但所有答案(在此页面上)都给了我12.34 for 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 包装原始数字,然后通过舍入输出乘以符号。

    【讨论】:

      【解决方案3】:

      Mongo 4.2 开始,有一个新的 $round 聚合运算符,可用于对 数字 以一定的精度精确到指定的小数

      { $round : [ , ] }

      可以在聚合管道中使用(这里我们将xs 舍入到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 位)。

      【讨论】:

      • 请注意,这将四舍五入。如果你想向上/向下/到零/到无穷大,它对你不起作用
      【解决方案4】:

      让我说,很遗憾 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 }
      

      【讨论】:

        【解决方案5】:
        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 替换为所需的十进制数)

        【讨论】:

          【解决方案6】:

          此解决方案正确地向上或向下舍入到 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 回答中的问题,即它只截断而不是 round up/down correctly;即使在 cmets 中建议的更改之后。
          3. 加法因子中尾随 9 的数量很大,以适应高精度输入数字。根据要四舍五入的数字的精度,加法因子可能需要比这更精确。

          【讨论】:

            【解决方案7】:
            {$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]}
            

            希望这些可以帮助四舍五入。

            【讨论】:

              【解决方案8】:

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

              说号码是3.3333333

              var round = require('mongo-round');
              
              db.myCollection.aggregate([
                  { $project: {
                      roundAmount: round('$amount', 2)  // it will become 3.33
                  } }
              ]);
              

              【讨论】:

                【解决方案9】:

                没有$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 }
                

                【讨论】:

                • 请注意,从技术上讲,这会将结果截断到小数点后两位。如果你想真正 round 它你应该在最后除以 100 之前添加 49。
                • Asya,您能否指出您的第一个代码块中的哪个变量/常量 db.a.save({x:1.23456789})....实际上是对数字进行四舍五入。我说的就像我们在 C# 中所做的那样,例如Math.Round(123.234, 2)。其中 2 是数字将四舍五入的小数位数。您也可以根据您上面提到的评论修改上面的代码(在最后除以 100 之前添加 49)。
                • 分步:给定 x 是一个数字,我将 x 乘以 100 并从 x*100,1 的模数中减去(这是乘法后的小数部分)。这给我留下了我想要保留的整数的 100 倍,所以我将它除以 100。1.5478*100=>154.78 mod 除以 1 是 0.78,所以减法后我有 154 除以 100。剩下 1.54 .对于 之后的不同小数位数。使用 10 的不同倍数而不是 100。
                • 未来读者请注意:从 3.2(2015 年底)开始,聚合框架将具有三个可用于此目的的运算符:$trunc(截断为 0)、$floor 和 $ceil。
                【解决方案10】:

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

                > 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 的建议并在您的应用程序中创建rounds。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2012-01-03
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-11-24
                  • 2012-03-11
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多