大量错误地在JavaScript中舍入

时间:2009-09-04 15:24:39

标签: javascript floating-point floating-accuracy ieee-754

请参阅此代码:

<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到814341252076979100。

编辑:请参阅下面的第一条评论。显然这不是关于JSON,而是关于JavaScript数字处理的东西。但问题仍然存在:

为什么会这样?

6 个答案:

答案 0 :(得分:59)

你在这里看到的实际上是两次舍入的效果。 ECMAScript中的数字在内部表示为双精度浮点数。当id设置为714341252076979033(十六进制为0x9e9d9958274c359)时,实际上会为其指定最近的可表示双精度值,即7143412520769790720x9e9d9958274c380) 。当您打印出该值时,它将被舍入为15个有效十进制数字,这将给出14341252076979100

答案 1 :(得分:50)

您的数字类型的容量超出了容量,详情请参阅§8.5 of the spec。这些ID需要是字符串。

IEEE-754双精度浮点(JavaScript使用的数字种类)不能精确地表示所有数字(当然)。众所周知,0.1 + 0.2 == 0.3是假的。这会影响整数,就像影响分数一样;一旦你达到9,007,199,254,740,991(Number.MAX_SAFE_INTEGER)以上就会开始。

超越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位精度)。如何使用指数是复杂的(described here),但在非常模糊的术语中,如果我们向指数添加一个,则有效数的值加倍,因为指数用于2的权力(再次,在那里警告,它不是直接的,那里有聪明)。

让我们看看价值9007199254740991(又名Number.MAX_SAFE_INTEGER):

   +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− sign bit
  / +−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exponent
 / /        |  +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+− significand
/ /         | /                                                  |
0 10000110011 1111111111111111111111111111111111111111111111111111
                = 9007199254740991 (Number.MAX_SAFE_INTEGER)

该指数值10000110011意味着每次我们向有效数字添加一个时,表示的数字会增加1(整数1,我们失去了更早表示小数的能力)。 / p>

但现在这个意义已经充实。要超过这个数字,我们必须增加指数,这意味着如果我们在有效数字上加一个,表示数字的值上升2,而不是1(因为指数应用于2,这是二进制浮点数):

   +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− sign bit
  / +−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exponent
 / /        |  +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+− significand
/ /         | /                                                  |
0 10000110100 0000000000000000000000000000000000000000000000000000
                = 9007199254740992 (Number.MAX_SAFE_INTEGER + 1)

嗯,没关系,因为9007199254740991 + 1无论如何都是9007199254740992。但!我们无法代表9007199254740993。我们已经用光了。如果我们只对有效数字添加1,它会将值加2:

   +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− sign bit
  / +−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exponent
 / /        |  +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+− significand
/ /         | /                                                  |
0 10000110100 0000000000000000000000000000000000000000000000000001
                = 9007199254740994 (Number.MAX_SAFE_INTEGER + 3)

格式不能再代表奇数,因为我们增加了值,指数太大了。

最终,我们再次耗尽有效位并且必须增加指数,所以我们最终只能表示4的倍数。然后是8的倍数。然后是16的倍数。依此类推。

答案 2 :(得分:8)

它不是由这个json解析器引起的。只需尝试输入714341252076979033到fbug的控制台。你会看到相同的714341252076979100。

有关详细信息,请参阅此博文: http://www.exploringbinary.com/print-precision-of-floating-point-integers-varies-too

答案 3 :(得分:5)

JavaScript使用双精度浮点值,即总精度为53位,但您需要

ceil(lb 714341252076979033) = 60

用于准确表示值的位。

最接近的完全可表示的数字是714341252076979072(用二进制写原始数字,用0替换最后7位数并向上舍入,因为最高的替换数字是1。 / p>

您将获得714341252076979100而不是此数字,因为ECMA-262,§9.8.1所描述的ToString()使用10的幂并且以53位精度工作,所有这些数字都是相等的。< / p>

答案 4 :(得分:4)

问题是您的号码需要比JavaScript更精确。

您可以将该号码作为字符串发送吗?分为两部分?

答案 5 :(得分:2)

JavaScript只能处理高达约9000万的精确整数(即9个,15个零)。高于此,你会得到垃圾。通过使用字符串来保存数字来解决这个问题。如果您需要使用这些数字进行数学运算,请编写自己的函数或查看是否可以为它们找到一个库:我建议使用前者,因为我不喜欢我见过的库。为了帮助您入门,请在another answer上查看我的两个功能。