为什么 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")
当给定的数字足够大(大于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 toNumberLong("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")