75

このコードを参照してください:

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に丸められます。

なぜこうなった?

4

6 に答える 6

85

JavaScriptのnumberタイプの容量がオーバーフローしています。詳細については、仕様の§8.5を参照してください。これらのIDは文字列である必要があります。

IEEE-754倍精度浮動小数点(JavaScriptが使用する種類の数値)は、(もちろん)すべての数値を正確に表すことはできません。有名なことに、それ0.1 + 0.2 == 0.3は誤りです。これは、小数に影響するのと同じように整数に影響を与える可能性があります。9,007,199,254,740,991(Number.MAX_SAFE_INTEGER)を超えると開始します。

Number.MAX_SAFE_INTEGER + 1( )を超える9007199254740992と、IEEE-754浮動小数点形式はすべての連続する整数を表すことができなくなります。9007199254740991 + 1ですが9007199254740992フォーマットで表現できないため9007199254740992 + 1です。次にできるのはです。その後、することはできませんが、できます。 90071992547409929007199254740993900719925474099490071992547409959007199254740996

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

値はそのしきい値をはるかに上回っているため、最も近い表現可能な値に丸められます。

ES2020では、BigInt任意の大きさの整数に使用できますが、JSON表現はありません。文字列とリバイバー関数を使用できます。

const jsonString = '{"id":"714341252076979033","type":"FUZZY"}';
// Note it's a string −−−−^−−−−−−−−−−−−−−−−−−^

const obj = JSON.parse(jsonString, (key, value) => {
    if (key === "id" && typeof value === "string" && value.match(/^\d+$/)) {
        return BigInt(value);
    }
    return value;
});

console.log(obj);
(Look in the real console, the snippets console doesn't understand BigInt.)


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

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

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

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

しかし今、その仮数はいっぱいです。その数を超えるには、指数を増やす必要があります。つまり、仮数に1を加えると、表される数の値は1ではなく2増加します(指数は2に適用されるため、この基数は2になります)。 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の倍数しか表現できなくなります。

于 2009-09-04T15:33:00.490 に答える
71

ここに表示されているのは、実際には2つの丸めの効果です。ECMAScriptの数値は、内部的に倍精度浮動小数点で表されます。を( 16進数で)にid設定すると、実際には最も近い表現可能な倍精度値(())が割り当てられます。値を出力すると、15桁の小数点以下が四捨五入されます。これにより、が得られます。7143412520769790330x9e9d9958274c3597143412520769790720x9e9d9958274c38014341252076979100

于 2009-09-04T15:48:25.223 に答える
9

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

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

于 2009-09-04T15:31:40.323 に答える
5

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

ceil(lb 714341252076979033) = 60

値を正確に表すビット。

最も近い正確に表現可能な数値は次のとおりです714341252076979072(元の数値を2進数で書き込み、最後の7桁をに置き換え、0置き換えられた最大の桁がだったため、切り上げ1ます)。

ECMA-262で説明されているように、§9.8.1は10の累乗で動作し、53ビットの精度ではこれらの数値はすべて等しい714341252076979100ため、この数値の代わりに取得できます。ToString()

于 2009-09-04T16:31:22.910 に答える
4

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

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

于 2009-09-04T15:34:20.083 に答える
2

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

于 2009-09-05T04:20:34.143 に答える