web-dev-qa-db-fra.com

Arrondir à 2 décimales à l'aide de la structure d'agrégation MongoDB

J'utilise le cadre d'agrégation mongodb et effectue certains calculs, comme indiqué ci-dessous 

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

}

); 

J'essaie de déterminer le meilleur et le plus simple moyen d'arrondir mes résultats à deux décimales. Voici mon résultat

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

}

Je veux que "ActToPln" affiche 70.59 au lieu de "ActToPln": 70.58823529411765 , dans les résultats du cadre de coordination lui-même. Je veux éviter de faire l'arrondi dans ma demande

Pouvez-vous s'il vous plaît aider avec les mêmes.

Voici le jeu de données que j'ai utilisé.

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

Merci d'avance. Nandu

17
user2552537

Il n'y a pas d'opérateur $round mais vous pouvez le faire dans le cadre d'agrégation - le faire dans un ordre spécifique évite généralement les problèmes de précision en virgule flottante.

> 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 }

Étant donné le pipeline existant dans le problème, remplacez:

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

avec

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

Avec vos exemples de points de données, voici le résultat:

{ "ActSls" : 31, "PlnSls" : 51, "ActToPln" : 60.78 }
{ "ActSls" : 41, "PlnSls" : 51, "ActToPln" : 80.39 }
{ "ActSls" : 72, "PlnSls" : 102, "ActToPln" : 70.58 }
15
Asya Kamsky

mongo-round fonctionne Nice. Le moyen le plus propre que j'ai trouvé.

Dites que le numéro est 3.3333333

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

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

Je ne sais pas pourquoi, mais toutes les réponses (sur cette page) me donnent 12.34 pour 12.345. J'ai donc écrit ma propre phase de projet:

x = 12.345

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

Cela donne 12.35.

Voici une arithmétique simple, sans astuce:

  1. 12.345 * 100 = 1234.5 # Cette étape nous amène à la position d'arrondi: 100 = 10 ^ 2 (deux signes après le point). L'étape sera équilibrée par l'étape 4.
  2. 1234.5 + 0.5 = 1235.0 # Ici, je reçois mon round half up
  3. tronqué (1235.0) = 1235 # Dépose simplement une partie fractionnaire
  4. 1235/100 = 12,35

Cependant, cela ne fonctionne pas correctement pour les négatifs (cela suffisait pour mon agrégation). Pour les deux cas (positif et négatif), vous devez l’utiliser avec abs:

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

Ici, j’obtiens le signe du nombre, encapsule le nombre original par des abs puis multiplie le signe en arrondissant la sortie.

2
egvo

Cette solution arrondit correctement à 2dp:

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

Par exemple, il arrondit 1,2499 à 1,25, mais 1,2501 à 1,25.

Remarques:

  1. Cette solution est basée sur les exemples donnés à http://www.kamsky.org/stupid-tricks-with-mongodb/rounding-numbers-in-aggregation-framework
  2. Dans sa réponse, Asya Kamsky résout le problème: elle ne fait que tronquer et ne pas arrondir correctement ; même après le changement suggéré dans les commentaires.
  3. Le nombre de 9 derniers dans le facteur d'addition est grand, afin de prendre en charge des nombres d'entrée de haute précision. En fonction de la précision des chiffres à arrondir, il peut être nécessaire de rendre le facteur d'addition encore plus précis.
2
Vince Bowdren

Il n'y a pas d'opérateur round dans la version actuelle de Aggregation Framework. Vous pouvez essayer cet extrait:

> 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
}

mais comme vous le voyez, cette solution ne fonctionne pas bien en raison de problèmes de précision. Le moyen le plus simple dans ce cas est de suivre les conseils de @wiredprairie et de créer des variables round dans votre application. 

2
Artem Mezhenin
rounded:{'$multiply': [{ "$cond": [{ "$gte": [ "$x", 0 ] }, 1,-1 ]},{'$divide': [{'$trunc': {'$add': [{'$multiply': [{'$abs': '$x'}, {$pow:[10,2]}]}, 0.5]}}, {$pow:[10,2]}]}]}

la solution d'egvo est cool mais donne une division par zéro si elle est nulle. Pour éviter, $ cond peut être utilisé pour détecter un signe

(Remplacez x par nom_zone et numéro 2 par le nombre décimal souhaité)

1
Brcn

Laissez-moi vous dire que c'est dommage que MongoDB manque cette fonction. J'espère qu'ils l'ajouteront bientôt.

Cependant, j'ai mis au point un long pipeline d'agrégation. En admettant, cela n’est peut-être pas efficace, mais cela respecte les règles d'arrondissement.

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"]}}                                    
                                ]
                            }
                        }
                    }
                }
            }
        }
    }
}])

Supposons que j'ai dans ma collection les documents suivants nommés t

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

Après avoir exécuté les requêtes ci-dessus, j'ai obtenu:

{ "number" : 2.342 }
{ "number" : 2.001 }
{ "number" : 2.001 }
0
Saleem
{$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]}

espérons que ceux-ci pourraient aider à arrondir.

0
Enrico Domingo