web-dev-qa-db-ja.com

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の数値処理に関するものです。しかし、問題は残っています:

なんでこんなことが起こっているの?

54
Jaanus

ここに表示されているのは、実際には2つの丸めの影響です。 ECMAScriptの数値は、内部的に倍精度浮動小数点で表されます。 id714341252076979033(16進数では0x9e9d9958274c359)に設定されている場合、実際には、最も近い表現可能な倍精度値である7143412520769790720x9e9d9958274c380 )。値を出力すると、有効数字15桁に丸められ、14341252076979100が得られます。

61
Stephen Canon

JavaScriptの数値型の容量がオーバーフローしています。詳細は §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 + 19007199254740992)を超えると、IEEE-754浮動小数点形式はすべての連続した整数を表すことができなくなります。 9007199254740991 + 19007199254740992ですが、9007199254740992 + 1でもあります90071992547409929007199254740993は形式で表されます。次は9007199254740994です。その場合、9007199254740995はできませんが、9007199254740996はできます。

その理由は、ビットが不足しているため、1のビットがなくなったためです。最下位ビットは2の倍数を表すようになります。最終的には、続行すると、そのビットが失われ、4の倍数でのみ機能します。

あなたの値はそのしきい値をwell超えているため、最も近い表現可能な値に丸められます。


ビットについて知りたい場合は、次のようになります。IEEE-754バイナリ倍精度浮動小数点数には、符号ビット、11ビットの指数(数値の全体的なスケールを2の累乗として定義する)があります。これはバイナリ形式であるためです])、および52ビットの仮数(ただし、形式は非常に賢いため、52ビットから53ビットの精度が得られます)。指数の使用方法は複雑です( ここで説明 )。ただし、指数に1を追加すると、very曖昧な用語で、指数は2のべき乗に使用されるため、仮数の値は2倍になります(ここでも、警告があります。直接ではなく、そこに賢さがあります)。

それでは、値9007199254740991(別名Number.MAX_SAFE_INTEGER)を見てみましょう。

 + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− − − − − − − − − − − − − − − − − − − −符号ビット
/+ − − − − − − − + − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − exponent 
// + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− + −仮数
// |/| 
 0 10000110011 1111111111111111111111111111111111111111111111111111 
 = 9007199254740991(Number.MAX_SAFE_INTEGER)

その指数値10000110011は、仮数に1を加えるたびに、表される数値が1ずつ増加することを意味します(整数1は、分数をより早く表す能力を失いました)。

しかし今、その仮数はいっぱいです。その数を超えるには、指数を増やす必要があります。つまり、仮数に1を加えると、表される数の値は1ではなく2増加します(指数が2に適用されるため、 2進浮動小数点数):

 + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− − − − − − − − − − − − − − − − − − − −符号ビット
/+ − − − − − − − + − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − exponent 
// + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− + −仮数
// |/| 
 0 10000110100 0000000000000000000000000000000000000000000000000000 
 = 9007199254740992(Number.MAX_SAFE_INTEGER + 1)

とにかく、9007199254740991 + 19007199254740992なので、問題ありません。だが! 9007199254740993を表すことはできません。ビットが足りなくなりました。仮数に1を追加すると、値に2が追加されます。

 + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− − − − − − − − − − − − − − − − − − − −符号ビット
/+ − − − − − − − + − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − exponent 
// + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− + −仮数
// |/| 
 0 10000110100 0000000000000000000000000000000000000000000000000001 
 = 9007199254740994(Number.MAX_SAFE_INTEGER + 3)

値を大きくすると、形式は奇数を表すことができなくなり、指数が大きすぎます。

最終的に、再び有効ビットが足りなくなり、指数を増やす必要があるため、4の倍数、8の倍数、16の倍数などしか表現できなくなります。

51
T.J. Crowder

このjsonパーサーが原因ではありません。 fbugのコンソールに714341252076979033と入力してみてください。同じ714341252076979100が表示されます。

詳細については、このブログ投稿を参照してください: http://www.exploringbinary.com/print-precision-of-floating-point-integers-varies-too

9
thorn̈

JavaScriptは倍精度の浮動小数点値、つまり53ビットの合計精度を使用しますが、

_ceil(lb 714341252076979033) = 60
_

値を正確に表すビット。

最も近い正確に表現可能な数値は_714341252076979072_です(元の数値をバイナリで記述し、最後の7桁を_0_に置き換えて、置き換えられた最上位の桁が_1_であるため切り上げます)。

ECMA-262の9.8.1で規定されているToString()は10の累乗で機能し、53ビット精度ではこれらの数値はすべて等しいため、この数値の代わりに_714341252076979100_を取得します。

5
Christoph

問題は、あなたの数値がJavaScriptよりも高い精度を必要とすることです。

数値を文字列として送信できますか? 2つの部分に分かれていますか?

4
Esteban Küber

JavaScriptは、最大約9000百万(9は15のゼロ)までの正確な整数しか処理できません。それより高いとゴミが出ます。文字列を使用して数値を保持することにより、これを回避します。これらの数値を使って計算する必要がある場合は、独自の関数を作成するか、それらのライブラリーを見つけることができるかどうかを確認してください。はじめに、- 別の答え にある2つの関数を参照してください。

2
Robert L