JavaScript 中的大数字被错误地舍入
看这段代码:
var jsonString = '{"id":714341252076979033,"type":"FUZZY"}';
var jsonParsed = JSON.parse(jsonString);
console.log(jsonString, jsonParsed);
当我在 Firefox 3.5 中看到控制台时,jsonParsed
的值是四舍五入的数字:
Object id=714341252076979100 type=FUZZY
尝试了不同的值,相同的结果(四舍五入的数字)。
我也不明白它的舍入规则。 714341252076979136 四舍五入为 714341252076979200,而 714341252076979135 四舍五入为 714341252076979100。
为什么会发生这种情况?
See this code:
var jsonString = '{"id":714341252076979033,"type":"FUZZY"}';
var jsonParsed = JSON.parse(jsonString);
console.log(jsonString, jsonParsed);
When I see my console in Firefox 3.5, the value of jsonParsed
is the number rounded:
Object id=714341252076979100 type=FUZZY
Tried different values, the same outcome (number rounded).
I also don't get its rounding rules. 714341252076979136 is rounded to 714341252076979200, whereas 714341252076979135 is rounded to 714341252076979100.
Why is this happening?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
您溢出了 JavaScript
number
类型的容量,请参阅 规范的§8.5 和 关于 IEEE-754 double 的维基百科页面- 精度二进制浮点数了解详细信息。这些 ID 需要是字符串。IEEE-754 双精度浮点(JavaScript 使用的数字类型)无法精确表示所有数字(当然)。众所周知,
0.1 + 0.2 === 0.3
是错误的。这会影响整数,就像影响小数一样;一旦您超过 9,007,199,254,740,991 (Number.MAX_SAFE_INTEGER
),它就会开始。除了
Number.MAX_SAFE_INTEGER + 1
(9007199254740992
) 之外,IEEE-754 浮点格式无法再表示每个连续的整数。9007199254740991 + 1 + 1 是
无法以该格式表示。下一个可能是9007199254740992
,但9007199254740992 + 1
也是9007199254740992
因为 < code>90071992547409939007199254740994
。那么9007199254740995
不能,但9007199254740996
可以。原因是我们已经用完了位,所以我们不再有 1 位;最低位现在代表 2 的倍数。最终,如果我们继续下去,我们会失去该位,并且只能以 4 的倍数工作。依此类推。
您的值远远高于该阈值,因此它们会四舍五入到最接近的可表示值。
从 ES2020 开始,您可以使用
BigInt
用于任意大的整数,但它们没有 JSON 表示形式。您可以使用字符串和 reviver 函数:
如果您对这些位感到好奇,会发生以下情况:IEEE-754 二进制双精度浮点数有一个符号位,即 11 位指数(它定义了数字的总体范围,作为 2 的幂 [因为这是二进制格式])和 52 位有效数(但该格式非常聪明,它从这 52 位中获得了 53 位精度)。指数的使用方式很复杂(此处描述),但用非常的术语来说,如果我们给指数加一,有效数的值就会加倍,因为指数用于 2 的幂(再次强调,它不是直接的,里面有聪明之处)。
让我们看看值
9007199254740991
(又名Number.MAX_SAFE_INTEGER
):该指数值
10000110011
意味着每次我们加一有效数,表示的数字增加 1(整数 1,我们很早就失去了表示小数的能力)。但现在该有效数已满。要超过该数字,我们必须增加指数,这意味着如果我们在有效数上加 1,则表示的数字的值会增加 2,而不是 1(因为指数应用于 2,即该数字的底数)二进制浮点数):
好吧,没关系,因为
9007199254740991 + 1
无论如何都是9007199254740992
。但!我们无法代表9007199254740993
。我们的位已经用完了。如果我们只在尾数上加 1,它就会在值上加 2:当我们增加值时,格式就不能再表示奇数了,指数太大了。
最终,我们再次用完了有效位数,并且必须增加指数,因此我们最终只能表示 4 的倍数。然后是 8 的倍数。然后是 16 的倍数。依此类推。
You're overflowing the capacity of JavaScript's
number
type, see §8.5 of the spec and the Wikipedia page on IEEE-754 double-precision binary floating point for details. Those IDs will need to be strings.IEEE-754 double-precision floating point (the kind of number JavaScript uses) can't precisely represent all numbers (of course). Famously,
0.1 + 0.2 === 0.3
is false. That can affect whole numbers just like it affects fractional numbers; it starts once you get above 9,007,199,254,740,991 (Number.MAX_SAFE_INTEGER
).Beyond
Number.MAX_SAFE_INTEGER + 1
(9007199254740992
), the IEEE-754 floating-point format can no longer represent every consecutive integer.9007199254740991 + 1
is9007199254740992
, but9007199254740992 + 1
is also9007199254740992
because9007199254740993
cannot be represented in the format. The next that can be is9007199254740994
. Then9007199254740995
can't be, but9007199254740996
can.The reason is we've run out of bits, so we no longer have a 1s bit; the lowest-order bit now represents multiples of 2. Eventually, if we keep going, we lose that bit and only work in multiples of 4. And so on.
Your values are well above that threshold, and so they get rounded to the nearest representable value.
As of ES2020, you can use
BigInt
for integers that are arbitrarily large, but there is no JSON representation for them. You could use strings and a reviver function:If you're curious about the bits, here's what happens: An IEEE-754 binary double-precision floating-point number has a sign bit, 11 bits of exponent (which defines the overall scale of the number, as a power of 2 [because this is a binary format]), and 52 bits of significand (but the format is so clever it gets 53 bits of precision out of those 52 bits). How the exponent is used is complicated (described here), but in very vague terms, if we add one to the exponent, the value of the significand is doubled, since the exponent is used for powers of 2 (again, caveat there, it's not direct, there's cleverness in there).
So let's look at the value
9007199254740991
(aka,Number.MAX_SAFE_INTEGER
):That exponent value,
10000110011
, means that every time we add one to the significand, the number represented goes up by 1 (the whole number 1, we lost the ability to represent fractional numbers much earlier).But now that significand is full. To go past that number, we have to increase the exponent, which means that if we add one to the significand, the value of the number represented goes up by 2, not 1 (because the exponent is applied to 2, the base of this binary floating point number):
Well, that's okay, because
9007199254740991 + 1
is9007199254740992
anyway. But! We can't represent9007199254740993
. We've run out of bits. If we add just 1 to the significand, it adds 2 to the value:The format just cannot represent odd numbers anymore as we increase the value, the exponent is too big.
Eventually, we run out of significand bits again and have to increase the exponent, so we end up only being able to represent multiples of 4. Then multiples of 8. Then multiples of 16. And so on.
您在这里看到的实际上是两次舍入的效果。 ECMAScript 中的数字在内部表示为双精度浮点数。当
id
设置为714341252076979033
(十六进制的0x9e9d9958274c359
)时,它实际上被分配了最接近的可表示的双精度值,即714341252076979072
(0x9e9d9958274c380
)。当您打印该值时,它会四舍五入到 15 位有效十进制数字,即14341252076979100
。What you're seeing here is actually the effect of two roundings. Numbers in ECMAScript are internally represented double-precision floating-point. When
id
is set to714341252076979033
(0x9e9d9958274c359
in hex), it actually is assigned the nearest representable double-precision value, which is714341252076979072
(0x9e9d9958274c380
). When you print out the value, it is being rounded to 15 significant decimal digits, which gives14341252076979100
.这个 JSON 解析器不会导致它。只需尝试在 fbug 的控制台中输入
714341252076979033
即可。您会看到相同的714341252076979100
。有关详细信息,请参阅此博客文章:floating-点This JSON parser does not cause it. Just try to enter
714341252076979033
to fbug's console. You'll see the same714341252076979100
. See this blog post for details: floating-pointJavaScript 使用双精度浮点值,即总精度为 53 位,但您需要
位来精确表示该值。
最接近的完全可表示的数字是
714341252076979072
(以二进制形式写入原始数字,用0
替换最后 7 位数字并向上舍入,因为最高替换数字是1< /代码>)。
您将得到
714341252076979100
而不是这个数字,因为 ECMA-262、§9.8.1 中描述的ToString()
使用 10 的幂并且以 53 位精度工作所有这些数量相等。JavaScript uses double precision floating point values, ie a total precision of 53 bits, but you need
bits to exactly represent the value.
The nearest exactly representable number is
714341252076979072
(write the original number in binary, replace the last 7 digits with0
and round up because the highest replaced digit was1
).You'll get
714341252076979100
instead of this number becauseToString()
as described by ECMA-262, §9.8.1 works with powers of ten and in 53 bit precision all these numbers are equal.问题是你的数字需要比 JavaScript 更高的精度。
您可以将号码作为字符串发送吗?分成两部分?
The problem is that your number requires a greater precision than JavaScript has.
Can you send the number as a string? Separated in two parts?
JavaScript 只能处理最多大约 90 亿个整数(即 9 个数字和 15 个零)。高于这个值,你就会得到垃圾。通过使用字符串来保存数字来解决这个问题。如果您需要对这些数字进行数学运算,请编写自己的函数或看看是否可以找到它们的库:我建议前者,因为我不喜欢我见过的库。为了帮助您开始,请参阅我的两个函数 另一个答案< /a>.
JavaScript can only handle exact whole numbers up to about 9000 million million (that's 9 with 15 zeros). Higher than that and you get garbage. Work around this by using strings to hold the numbers. If you need to do math with these numbers, write your own functions or see if you can find a library for them: I suggest the former as I don't like the libraries I've seen. To get you started, see two of my functions at another answer.