大量错误地在Javascript中舍入
看到这个代码:
<html> <head> <script src="http://www.json.org/json2.js" type="text/javascript"></script> <script type="text/javascript"> var jsonString = '{"id":714341252076979033,"type":"FUZZY"}'; var jsonParsed = JSON.parse(jsonString); console.log(jsonString, jsonParsed); </script> </head> <body> </body> </html>
当我在Firefox 3.5中看到我的控制台时,jsonParsed的值是:
Object id=714341252076979100 type=FUZZY
即数字四舍五入。 尝试不同的价值观,相同的结果(数字四舍五入)。
我也没有得到它的舍入规则。 714341252076979136四舍五入为714341252076979200,而714341252076979135四舍五入为714341252076979100。
编辑:请参阅下面的第一条评论。 显然这不是关于JSON,而是关于Javascript数字处理。 但问题依然存在:
为什么发生这种情况?
你在这里看到的实际上是两个圆angular的影响。 ECMAScript中的数字在内部表示为双精度浮点数。 当id
设置为714341252076979033
(hex中的0x9e9d9958274c359
)时,它实际上被分配了最接近的可表示的双精度值,即714341252076979072
( 0x9e9d9958274c380
)。 当你打印出这个值时,它被四舍五入成十五个十进制数字,这就给出了14341252076979100
。
您正在溢出JavaScript数字types的容量,请参阅规范的第8.5节了解详细信息。 这些ID将需要string。
IEEE-754双精度浮点(JavaScript使用的数字types)不能精确地表示所有数字(当然)。 有趣的是, 0.1 + 0.2 == 0.3
是错误的。 这可能会影响整数,就像影响分数一样; 一旦您获得9,007,199,254,740,991( Number.MAX_SAFE_INTEGER
)以上,它就会启动。
Beyond Number.MAX_SAFE_INTEGER + 1
( 9007199254740992
),IEEE-754浮点格式不能再代表每个连续的整数。 9007199254740991 + 1
是9007199254740992
,但是9007199254740992 + 1
也是 9007199254740992
因为9007199254740993
不能用格式表示。 接下来可以是9007199254740994
。 那么9007199254740995
不能,但9007199254740996
可以。
原因是我们已经用完了,所以我们不再有一个1位; 最低位现在代表2的倍数。最终,如果我们继续前进,那么我们就会失去这一点,只能以4的倍数工作。依此类推。
你的值远高于这个阈值,所以他们四舍五入到最接近的可表示的值。
如果您对这些位感到好奇,那么会发生什么情况:IEEE-754二进制双精度浮点数有一个符号位,11位指数(定义数字的整体范围,是2 [因为这是一个二进制格式])和52位的有效位(但格式非常聪明,在52位中得到53位精度)。 如何使用指数是复杂的( 在这里描述 ),但在非常模糊的条件下,如果我们加上一个指数,有效数的值是加倍,因为指数用于2的幂(同样,注意,它是不直接,那里有聪明)。
所以让我们看一下9007199254740991
(又名: Number.MAX_SAFE_INTEGER
)的值:
+ ------------------------------------------------- --------------签位 / + ------- + ---------------------------------------- --------------指数 / / | + ------------------------------------------------- + - 有意义 / / | / | 0 10000110011 1111111111111111111111111111111111111111111111111111 = 9007199254740991(Number.MAX_SAFE_INTEGER)
指数值10000110011
意味着每次我们给有效数添加一个数,代表的数就增加1(整数1,我们失去了更早地表示分数的能力)。
但是现在这个重要的东西已经满了。 要超过这个数字,我们必须增加指数,这意味着如果我们加上一个有效数字,表示的数字的值上升2,而不是1(因为指数应用到2,这个基数二进制浮点数):
+ ------------------------------------------------- --------------签位 / + ------- + ---------------------------------------- --------------指数 / / | + ------------------------------------------------- + - 有意义 / / | / | 0 10000110100 0000000000000000000000000000000000000000000000000000 = 9007199254740992(Number.MAX_SAFE_INTEGER + 1)
那么,没关系,因为9007199254740991 + 1
是9007199254740992
。 但! 我们不能代表9007199254740993
。 我们已经用完了。 如果我们在有效数字中只加1,那么它将2加到数值上:
+ ------------------------------------------------- --------------签位 / + ------- + ---------------------------------------- --------------指数 / / | + ------------------------------------------------- + - 有意义 / / | / | 0 10000110100 0000000000000000000000000000000000000000000000000001 = 9007199254740994(Number.MAX_SAFE_INTEGER + 3)
当我们增加值时,格式不再代表奇数,指数太大。
最终,我们又耗尽了有效位,必须增加指数,所以我们最终只能表示4的倍数,然后是8的倍数,然后是16的倍数。等等。
这不是由这个jsonparsing器引起的。 试着input714341252076979033到fbug的控制台。 你会看到相同的714341252076979100。
有关详细信息,请参阅此博客文章: http : //www.exploringbinary.com/print-precision-of-floating-point-integers-varies-too
JavaScript使用双精度浮点值,即53位的总精度,但是您需要
ceil(lb 714341252076979033) = 60
位来精确表示值。
最接近的可表示的数字是714341252076979072
(用二进制表示原始数字,用0
代替最后的7位数字,因为最高的被replace的数字是1
)。
你会得到714341252076979100
而不是这个数字,因为如ECMA-262§9.8.1所描述的ToString()
以十位和五十三位精度工作,所有这些数字是相等的。
你的问题是你的数字需要比JavaScript更大的精度。
你可以发送一个string的数字? 分成两部分?
JavaScript只能处理大约9000万的确切整数(即9个15个零)。 比这更高,你会得到垃圾。 通过使用string来解决这个问题。 如果你需要用这些数字进行math计算,请写下你自己的函数,或者看看是否可以为他们find一个图书馆:我build议前者,因为我不喜欢我见过的图书馆。 为了让你开始,在另一个答案看到我的两个function。