【问题标题】:Floating Point Numbers Do Not Match In Aggregation浮点数在聚合中不匹配
【发布时间】:2017-11-25 09:55:31
【问题描述】:

我有这个样本测试收集文件:

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

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

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

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

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

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

我在这里做错了吗?

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework


    【解决方案1】:

    您的问题并没有真正说出全部真相。如果我采用最初的 "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 }
    

    【讨论】:

    • 感谢 Neil 的回复,但我发布了测试集合中存在的正确文档及其 mongo 查询结果。您的解决方法确实有效,但尤其是在尝试不同的十进制数时,它对于构造 mongo 算术表达式没有用。
    • @SagarRathod 是的,它确实有效。它们是浮点数,这就是您处理它们的方式。 完全按照我的例子。我使用的是您发布的相同数据。
    • @SagarRathod 我现在明白你的困惑了。您正在 Robomongo ( Robo 3T) 中运行此程序,您在问题中呈现结果的方式应该可以证明这一点。它有问题,不能正确显示浮点数。如果您使用普通发行版中包含的普通mongo shell 运行,那么您会看到与我发布的相同的输出。尽管如此,即使在 Robo 3T 中,更正后的聚合语句也会选择正确的数据。
    • @SagarRathod 您还需要了解,这不是 MongoDB 错误也不是解决方法。这实际上是您“应该”处理浮点数的方式。正如答案中链接中包含的著名文章中所深入解释的那样。
    • @SagarRathod 仅供参考,我用 Robo 3T github.com/Studio3T/robomongo/issues/1393 提出了这个错误。但同样,这是 MongoDB 的正常行为。如果您要使用浮点数,那么您应该像我展示的那样将整数分解为整数。这是常见的做法
    【解决方案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
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-11
      • 2018-02-17
      • 1970-01-01
      • 1970-01-01
      • 2015-10-30
      • 1970-01-01
      相关资源
      最近更新 更多