查看此代码:
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。
为什么会这样呢?
6条答案
按热度按时间iyfjxgzm1#
JavaScript的
number
类型的容量已经溢出,参见规范的§ 8.5节了解详细信息,这些ID必须是字符串。IEEE-754双精度浮点数(JavaScript使用的数字类型)不能精确地表示所有数字(当然),众所周知,
0.1 + 0.2 == 0.3
是false,它可以像影响分数一样影响整数;当你的值超过9,007,199,254,740,991(Number.MAX_SAFE_INTEGER
)时,它就会开始。超越
Number.MAX_SAFE_INTEGER + 1
(9007199254740992
),IEEE-754浮点格式不能再表示每个连续整数。9007199254740991 + 1
是9007199254740992
,但9007199254740992 + 1
也是9007199254740992
,因为9007199254740993
不能用该格式表示。下一个可以是9007199254740994
。那么9007199254740995
就不能是,但是9007199254740996
可以。原因是我们已经用完了位,所以我们不再有1位;最低位现在表示2的倍数。如果继续,最终会丢失该位,只能以4的倍数工作。依此类推。
您的值远远高于该阈值,因此它们会四舍五入到最接近的可表示值。
从ES2020开始,可以使用
BigInt
来表示任意大的整数,但没有JSON表示。可以使用字符串和reviver函数:如果你对这些细节感兴趣,下面是发生的事情:IEEE-754二进制双精度浮点数有一个符号位、11位指数(它将数字的总小数位数定义为2的幂[因为这是二进制格式])和52位有效数(但是这种格式非常巧妙,它可以从52位中获得53位精度)。指数的使用非常复杂(described here),但是用非常模糊的术语来说,如果我们给指数加1,则有效位的值会加倍,因为指数用于表示2的幂(再次提醒,这不是直接的,其中有一些技巧)。
我们来看一下
9007199254740991
(又名Number.MAX_SAFE_INTEGER
)的值:该指数值
10000110011
意味着每次我们向有效数加1,所表示的数字就增加1(整数1,我们很早就失去了表示小数的能力)。但现在有效位已满,为了超过该数字,我们必须增加指数,这意味着如果我们在有效位上加1,则所表示的数字的值增加2,而不是1(因为指数应用于2,即该二进制浮点数的基数):
好吧,没关系,因为
9007199254740991 + 1
就是9007199254740992
,但是,我们不能表示9007199254740993
,我们已经没有位了,如果我们只在有效数上加1,它就会在值上加2:这个格式不能再表示奇数了,因为我们增加了这个值,指数太大了。
最后,我们又用完了有效位,必须增加指数,所以我们只能表示4的倍数,然后是8的倍数,然后是16的倍数,依此类推。
ghhaqwfi2#
你在这里看到的实际上是两次舍入的效果。ECMAScript中的数字在内部用双精度浮点表示。当
id
设置为714341252076979033
时(十六进制0x9e9d9958274c359
),则实际上会为其分配最接近的可表示双精度值,即714341252076979072
(0x9e9d9958274c380
)。当您打印出该值时,它被四舍五入为15个有效的十进制数字,从而得到14341252076979100
。svdrlsy43#
这个JSON解析器不会导致这种情况,只要尝试在fbug的控制台中输入
714341252076979033
,你就会看到相同的714341252076979100
,详情请参见这篇博客文章:floating-pointnxowjjhe4#
JavaScript使用双精度浮点值,即总精度为53位,但您需要
位精确表示该值。
最接近的可精确表示的数字是
714341252076979072
(用二进制写原始数字,用0
替换最后7位数字,并向上舍入,因为替换的最高位数字是1
)。你会得到
714341252076979100
而不是这个数字,因为ECMA-262 §9.8.1中描述的ToString()
是10的幂,在53位精度下所有这些数字都相等。h9a6wy2h5#
问题是您的数字需要比JavaScript更高的精度。
你能把这个数字作为一个字符串发送吗?分成两部分?
vhmi4jdf6#
JavaScript只能处理90亿以内的整数(即9加上15个零)。超过这个数字,你就会得到垃圾。解决这个问题的方法是使用字符串来保存数字。如果你需要用这些数字做数学运算,编写你自己的函数,或者看看你是否能找到一个函数库来保存它们:我建议使用前者,因为我不喜欢我所看到的库。为了让你开始,看看我的两个函数在另一个答案。