大量错误地在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 )时,它实际上被分配了最接近的可表示的双精度值,即7143412520769790720x9e9d9958274c380 )。 当你打印出这个值时,它被四舍五入成十五个十进制数字,这就给出了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 + 19007199254740992 ),IEEE-754浮点格式不能再代表每个连续的整数。 9007199254740991 + 19007199254740992 ,但是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 + 19007199254740992 。 但! 我们不能代表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。