読者です 読者をやめる 読者になる 読者になる
スポンサーリンク

JavaScriptの動かないコード(中級編)大きな数字を表示できないエラー (2の53乗を超えると,IEEE754の精度保証外なので計算もalertも不可能)

javascript 動かないコード 数値計算


以下のJavaScriptコードが意図した動作をしないのは,なぜですか。(制限時間1分)


やりたい事:

  • 2の55乗を計算する。(正しい値は 36028797018963968 )
<input type="button" value="2の55乗を計算して表示" onclick="f()">

<script>

function f(){
	
alert( 
	"「2の55乗は36028797018963968」と表示します。\n\n" 
		
	+ "「2の55乗は" + Math.pow( 2, 55 ) + "」\n\n"
		
	+ "「2の55乗は" + 36028797018963968 + "」\n\n"
		
	+ "※ちなみに2を何乗しても1の位は0にならないはずです。"
);

}

</script>




答え


発生する不具合

下記のように表示されてしまう。

「2の55乗は36028797018963968」と表示します。

「2の55乗は36028797018963970」

「2の55乗は36028797018963970」

末尾が0であることに注目しよう。

36028797018963968 ではなく,
36028797018963970 となってしまう。

2ずれているのだ。


Math.powによるべき乗の計算結果もダメだし,

それどころか普通にalertすることすらできない。

なんと,下記のシンプルなコードすら失敗する。

alert( 36028797018963968 );

// → 36028797018963970 と表示されてしまう


「大きな数の計算」,そして「大きな数の表示」に失敗している。

理由

原因は,JavaScriptで扱える最大の数は,2の53乗までだから


JavaScriptでは,内部的に数値を「IEEE754」という規格に従って

64ビット倍精度」で保管している。


この規格で処理・表現できる最大値が,2の53乗 - 1なのだ。

最大値をオーバーした瞬間,正確さは保証されなくなる。

一番上の桁の正確さ(=有効数字)を保とうとする結果,一番下の桁から正確さが失われていく。


これはバグではなく,エラーでもなく,JavaScriptの仕様だ。

というより,コンピュータを使った数値計算の常識でもある。

Number.isSafeInteger() - JavaScript | MDN
https://developer.mozilla.org/ja/docs...

  • 2^53はsafe integerではありません。 IEEE-754で正確に表現されますが、整数値2^53 + 1は直接IEEE-754では表現できません。代わりに四捨五入、0への丸めによって2^53 に丸められます。 safe integerは-(2^53 - 1)以上, 2^53 - 1以下の整数値です。


数値 | JavaScript プログラミング解説
http://so-zou.jp/web-app/tech/program...

  • JavaScriptは整数と浮動小数点数を区別せず、すべての数値は浮動小数点数として扱われます。
  • 整数として精度が保証されるのは、-2^53 (-9,007,199,254,740,992) 〜2^53 (9,007,199,254,740,992) までです。その範囲外では演算結果に誤差が生じます。


JavaScript 型の解説 - Wikibooks
http://ja.wikibooks.org/wiki/JavaScri...

  • JavaScript標準では64ビット浮動小数点型の数値の限りを扱うことになっており、その大きさは整数なら±2の52乗の範囲を扱える。 (10進数では10の15乗程度)
  • また、これを超える数も扱えるが(10進法で320桁程度)、小数点以下15桁以上は切り捨てられ、対数表示で表現される。 小数も、正確に表現される桁数はそこまでである。


なぜJavaScriptで「76287755398823936」が正しく表示できないか、あるいはなぜRubyでも表せないか。 | ψ(プサイ)の興味関心空間
http://ledyba.org/2011/07/02211155.php

  • doubleのデータは符号、仮数、指数の部分に分けられて、  (-1)符号 * 仮数 * 2指数


やっぱりdoubleでは「76287755398823936」は表現できない | ψ(プサイ)の興味関心空間
http://ledyba.org/2011/07/02232756.php

  • 62877553988239**という桁までは確定、その次の桁は2か3か4、最後の下一桁はさっぱり☆わからない、というわけですが、stringに変換する際はどれかには決めないといけません。この時に最後を40(上側)とするのがChrome/(とたぶんFirefoxとRuby)、30(真ん中)に多分するのがIE8/9、ということになります。でもIEの挙動はなんか標準じゃ無いっぽい


JavaScriptの大きな数と小さな数の仕組みを理解する 〜 IEEE754入門 〜 - 風と宇宙とプログラム
http://d.hatena.ne.jp/mindcat/2009110...

  • 整数として正確に表現できる最大の値はいくつか?正確に表現できるというのは、n + 1 が確かに n + 1になることとします。n が非常に大きいときには、n + 1 は桁落ちが発生するので n のままです。
  • これらのことはJavaScriptが採用している数値表現であるIEEE754から当然の帰結となります。


414032 – Large integers (2^55) become different values when written as strings
https://bugzilla.mozilla.org/show_bug...

  • document.write(36028797018963968) writes 36028797018963970 (off by two). Larger integers result in larger errors.
  • numbers in ES3 are floating-point doubles with 52
  • ブラウザのバグとして報告されている。しかし,ecmascriptの仕様だからバグじゃないよと回答されている。


MongoDB types in Node.js - Stack Overflow
http://stackoverflow.com/questions/28...

  • JavaScript only has a Number type, which is, if the implementation follows the standard's recommendation, represented as IEEE-754 double precision floating point number.
  • It's also a good compromise, because they have integer precision up to 2^53 - 1, so you rarely need something outside this range, most of the time, and if you do, a long usually doesn't cut it either...


Issue 1777 - v8 - possible bug involving large ints in parseInt() - V8 JavaScript Engine - Google Project Hosting
https://code.google.com/p/v8/issues/d...

  • There are no integers in JavaScript. All numbers are virtually double-precision floating point numbers.
  • What you observe is rounding errors. If you need to work with big integer numbers, please consider using one of "BigInt" JS libraries.


IEEE 754 - Wikipedia
http://ja.wikipedia.org/wiki/IEEE_754...

  • 64ビット倍精度の仕様


Number.MAX_VALUE - 利用可能な最大値を得る - JavaScriptリファレンス
http://javascriptist.net/ref/Number.M...

  • 「Number.MAX_VALUEの動作デモ」

ちなみに,2^53 - 1 を超えた時点で精度の保証外だが,
2^53と2^54は,たまたま計算がうまくいく。

しかしこれは,たまたまであることをお忘れなく。

べき乗の計算であきらかな誤差が現れるのは,2^55から。

JavaScriptで2のべき乗を計算し,大きい数の場合に数値表現と文字列表現の食い違いをリストアップする実験コード
http://source-code-student.hatenablog.jp/entry/20141221/p2

  • おもしろい結果になった。 2の54乗までは,正確に計算と表示ができている。
  • 2の55乗から2の69乗までは,見かけは普通の整数だが,文字表現では下位桁が0に丸められている。数値としての情報と,文字列表示としての情報が異なる。
  • 2の70乗から先は,xe+yの形式で指数表示されてしまう。それにもかかわらず,一つ前のべきの2倍であることが保証されており,1の桁も正しく求められている。つまり,表示は指数表示になったものの,数値情報は内部的に正しく保持されているように見える。


2の51乗 - 100乗
http://cute.sh/solairo/d/00/02_051.html

  • 2^54 18014398509481984
  • 2^55 36028797018963968

このエラーと似たエラー

冒頭のコードは「巨大な整数」だが,「小数の丸め誤差」という観点でも似たエラーが起こる。

JavaScriptの動かないコード (中級編) かけ算を間違える
http://language-and-engineering.hatenablog.jp/entry/20080913/1221303278

  • JavaScriptが,足し算や掛け算を間違えているように見える。
  • この原因は,コンピュータの内部で,浮動小数点数の計算が二進数によって行なわれているという点にある。 有限桁の十進数を,有限桁の二進数で表現できる保証はない。 したがって,数値が二進数に変換された時点で,数値計算で言うところの丸め誤差が生じる。
  • 丸め誤差とは,桁数を省いたことによって生じる誤差である。 0.1も0.7も,計算時には実際の値とわずかに異なる値の二進数が用いられる。

このエラーの回避策

いちおう,.toFixed() すれば精度を保証できる。

alert( 
	"「2の55乗は36028797018963968」と表示します。\n\n" 
		
	+ "「2の55乗は" + Math.pow( 2, 55 ).toFixed(0) + "」\n\n"
		
	+ "「2の55乗は" + (36028797018963968).toFixed(0) + "」\n\n"
);

これなら,誤差が生じることなく 36028797018963968 という正しい値を表示できる。