浮点数在聚合中不匹配

时间:2017-06-22 08:21:58

标签: mongodb mongodb-query aggregation-framework

我有这个示例测试集合文档:

/* 1 */
{
    "_id" : 1.0,
    "value" : 10.7
}

/* 2 */
{
    "_id" : 2.0,
    "value" : 10.8
}

/* 3 */
{
    "_id" : 3.0,
    "value" : 10.7
}

所以当我在聚合管道中使用$ addFields时,使用查询在文档中添加新的“result”字段:

db.test.aggregate([{$addFields:{result:{ $add : ["$value",  .10]}}}]);

它给出了以下结果:

/* 1 */
{
    "_id" : 1.0,
    "value" : 10.7,
    "result" : 10.8
}

/* 2 */
{
    "_id" : 2.0,
    "value" : 10.8,
    "result" : 10.9
}

/* 3 */
{
    "_id" : 3.0,
    "value" : 10.7,
    "result" : 10.8
}

现在我想使用mongo查询与这个新添加的字段进行比较:

db.test.aggregate([
  {$addFields:{result:{ $add : ["$value",  .10]}}}, 
  { $match : { result : { $eq : 10.8}}}
]);

我看到的是上面的查询是正确的,但不确定为什么它没有返回匹配的文件?

我在这里做错了吗?

2 个答案:

答案 0 :(得分:2)

在你的问题中,你并没有真正说出全部真相。如果我采用最初的"value"字段并将其添加到0.1,那么结果就是我期望的结果:

所以插入文件:

db.numbers.insert([
  { "_id" : 1.0, "value" : 10.7  },
  { "_id" : 2.0, "value" : 10.8  },
  { "_id" : 3.0, "value" : 10.7  }
])

然后运行相同的初始聚合语句:

db.numbers.aggregate([
  { "$addFields":{
    "result": { "$add": [ "$value",  0.10 ] }
  }}
]);

结果:

{ "_id" : 1, "value" : 10.7, "result" : 10.799999999999999 }
{ "_id" : 2, "value" : 10.8, "result" : 10.9 }
{ "_id" : 3, "value" : 10.7, "result" : 10.799999999999999 }

欢迎来到计算机科学。这是浮点数学,它总是有舍入误差。如需完整阅读,请进入:

What Every Computer Scientist Should Know About Floating-Point Arithmetic

我们可以简单地通过不使用小数和舍入到因式整数来纠正这个问题。在这种情况下 x10

db.numbers.aggregate([
  { "$addFields": {
    "result": {
      "$divide": [
        { "$add": [
          { "$multiply": [ "$value", 10 ] },
          1
        ]},
        10
      ]
    }
  }}
])

"result"出现的地方是这样的:

{ "_id" : 1, "value" : 10.7, "result" : 10.8 }
{ "_id" : 2, "value" : 10.8, "result" : 10.9 }
{ "_id" : 3, "value" : 10.7, "result" : 10.8 }

最终$match仅限于要求的值:

db.numbers.aggregate([
  { "$addFields": {
    "result": {
      "$divide": [
        { "$add": [
          { "$multiply": [ "$value", 10 ] },
          1
        ]},
        10
      ]
    }
  }},
  { "$match": { "result": 10.8 } }
])

正确的结果

{ "_id" : 1, "value" : 10.7, "result" : 10.8 }
{ "_id" : 3, "value" : 10.7, "result" : 10.8 }

答案 1 :(得分:2)

JavaScript中的默认(当前也是唯一的)本机数字类型是双精度浮点Number。正如在关于这个问题的其他讨论中所指出的,二进制浮点算法受到舍入误差的影响,因为一些小数部分cannot be represented exactly in binary floating point。一个常见的解决方法是使用数据模型方法using a Scale Factor来避免存储小数值。

但是,如果您使用的是MongoDB 3.4+,那么在使用浮点或货币值时,您可以使用本机Decimal BSON type来提高精度。这实现了IEEE 754 Decimal 128 floating-point format,它支持精确的十进制表示,包括通过MongoDB的聚合管道进行算术操作。

mongo shell包含一个NumberDecimal帮助程序,用于将十进制值传递给聚合查询或CRUD命令。

设置一些示例数据:

db.test.insert([
    { "_id" : 1.0, "value" : NumberDecimal(10.7) },
    { "_id" : 2.0, "value" : NumberDecimal(10.8) }
])

...并使用聚合来比较原始值和递增结果:

db.test.aggregate([
    { $addFields: {
        "result"  : { "$add": [ "$value",  NumberDecimal(0.10) ] },
    }},

    // Compare value and result against expected value of 10.8
    { $addFields: {
        "matches_value":   { $eq: ["$value",  NumberDecimal(10.8)] },
        "matches_result":  { $eq: ["$result", NumberDecimal(10.8)] }
    }},
])

十进制类型正确保持精度,可用于完全匹配。此聚合的示例输出:

{
  "result": [
    {
      "_id": 1,
      "value": NumberDecimal("10.7000000000000"),
      "result": NumberDecimal("10.800000000000000"),
      "matches_value": false,
      "matches_result": true
    },
    {
      "_id": 2,
      "value": NumberDecimal("10.8000000000000"),
      "result": NumberDecimal("10.900000000000000"),
      "matches_value": true,
      "matches_result": false
    }
  ],
  "ok": 1
}