Подтвердить что ты не робот

Округление до 2 десятичных знаков с использованием агрегационной структуры MongoDB

Я использую структуру агрегации 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]}
  }

}

); 

Я пытаюсь выяснить, что является лучшим и простым способом округлить мои результаты до двух знаков после запятой. Ниже приведен мой результат

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

}

Я хочу, чтобы "ActToPln" отображал 70.59 вместо "ActToPln": 70.58823529411765 в результатах самой структуры aggegation. Я хочу, чтобы избежать округления в моем приложении

Можете ли вы помочь с тем же.

Ниже приведен набор данных, который я использовал.

{
    "_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
}

Спасибо заранее. Nandu

4b9b3361

Ответ 1

Нет оператора $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 }

Ответ 2

mongo-round работает хорошо. Самый чистый способ, который я нашел.

Скажите, что номер 3.3333333

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

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

Ответ 3

В текущей версии Aggregation Framework нет круглого оператора. Вы можете попробовать этот фрагмент:

> 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 в вашем приложении.

Ответ 4

Это решение правильно округляет или уменьшает до 2dp:

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

Например, он округляет 1.2499 вверх до 1.25, но 1.2501 вниз до 1.25.

Примечания:

  • Это решение основано на примерах, приведенных в http://www.kamsky.org/stupid-tricks-with-mongodb/rounding-numbers-in-aggregation-framework
  • Решает проблему в ответе Ася Камского, что она только усекает и не округлить вверх/вниз правильно; даже после внесения изменений в комментарии.
  • Число конечных 9s в коэффициенте добавления велико, для размещения высокоточных номеров ввода. В зависимости от точности номеров, которые должны быть округлены, необходимо добавить дополнительный коэффициент еще более точно, чем это.

Ответ 5

{$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]}

надеюсь, что это может помочь в округлении.

Ответ 6

Я не знаю, почему, но все ответы (на этой странице) дают мне 12.34 для 12.345. Поэтому я написал свой собственный проект:

x = 12.345

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

Он дает 12.35.

Вот простая арифметика, без трюков:

  • 12.345 * 100 = 1234.5 # Этот шаг позволяет нам округлить положение: 100 = 10 ^ 2 (два знака после точки). Шаг будет сбалансирован на шаг 4.
  • 1234.5 + 0.5 = 1235.0 # Здесь я получаю свой round half up
  • truncate (1235.0) = 1235 # Просто падает дробная часть
  • 1235/100 = 12,35

Однако для негативов он не работает корректно (этого было достаточно для моей агрегации). Для обоих (положительных и отрицательных) случаев вы должны использовать его с abs:

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

Здесь я получаю знак номера, обертываю исходное число абс, а затем умножаю знак, округляя вывод.

Ответ 7

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 с именем поля и номером 2 с нужным десятичным числом)

Ответ 8

Позвольте мне сказать, что стыдно, что 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 }