为什么 NumberLong(9007199254740993) 匹配 mongo shell 中 MongoDB 中的 NumberLong(9007199254740992)?

Why NumberLong(9007199254740993) matches NumberLong(9007199254740992) in MongoDB from mongo shell?

当给定的数字足够大(大于9007199254740992)时会出现这种情况,随着更多的测试,我什至发现许多相邻的数字可以匹配一个数字。

不仅 NumberLong(9007199254740996) 会匹配 NumberLong("9007199254740996"),而且 NumberLong(9007199254740995)NumberLong(9007199254740997).

当我想使用它的编号对记录进行操作时,我实际上可以使用三个不同的相邻编号来取回相同的记录。

采纳的答案有道理,下面引用最相关的部分:

Caveat: Don't try to invoke the constructor with a too large number, i.e. don't try db.foo.insert({"t" : NumberLong(1234657890132456789)}); Since that number is way too large for a double, it will cause roundoff errors. Above number would be converted to NumberLong("1234657890132456704"), which is wrong, obviously.

这里补充一下,让事情更清楚:

首先,Mongoshell是JavaScriptshell。而且JS不区分整数和浮点数。 JS 中的所有数字都表示为浮点值。这意味着 mongo shell 默认使用 64 位浮点数。如果 shell 看到 "9007199254740995",它会将其视为字符串并将其转换为 long long。但是当我们省略双引号时,mongo shell 将看到不带引号的 9007199254740995 并将其视为浮点数。

其次,JS使用IEEE 754标准定义的64位浮点数格式来表示数字,最大可以表示为:

,最小为:

实数有无穷多个,而JS浮点数格式能够准确表示的实数是有限的。这意味着当你在 JS 中处理实数时,数字的表示通常是实际数字的近似值。

这带来了所谓的舍入误差问题。因为整数也是用二进制浮点数表示的,所以尾数精度丢失的原因其实和小数一样。

JS数字格式可以让你准确表示

之间的所有整数

这里,由于数字大于9007199254740992,所以肯定会出现舍入误差。 NumberLong(9007199254740995)NumberLong(9007199254740996)NumberLong(9007199254740997)的二进制表示是一样的。所以当我们以这种方式查询这三个数字时,我们实际上是在查询同一件事。结果,我们将得到相同的记录。

我认为理解这个问题不是 JS 特有的很重要:它影响任何使用二进制浮点数的编程语言。

您误用了 NumberLong 构造函数。

正确的用法是给它一个字符串参数,如 relevant documentation.

中所述
NumberLong("2090845886852")