Numberの落とし穴
JavaScriptの論理演算
整数化
JavaScriptで小数を整数に直すとき、数学関数を使って
Math.floor(x)と書くことによって整数にすることができます。
これよりもっと短く、
x|0のように論理演算を用いて書くことによっても整数にすることができます。
"|"という演算子はビット単位OR演算子と呼ばれ、ビット毎の論理和を求めることができます。 0との論理和はどんな数(といっても0か1しかありませんが...)についても元の数が返ってくるため、論理和をxのビット毎に求めたらxが返ってくるのです。
例えば、10進数の(x=)13という数字は2進数では1101と表されるので、以下のように1011が求められます。0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | |
OR) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
= | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 |
この場合、xは自然数ですが、xが1.5のような小数の場合、小数点以下が自動的に切り捨てられるため、整数化ができるのです。
落とし穴その1
ここで、xとして非常に大きい数を入力してみましょう。
x = 0x200000006;
これは以下の10進数を16進数で表記したものです。
8589934598さて、ここで冒頭のx|0という式を実行すると、
6となります。85億という大きな数を入力したのに、なぜこんなに小さい値になってしまうのでしょうか。
それは、JavaScriptの論理演算は、32ビットの整数として計算するからです。小数点だけでなく、32ビットで表現できる範囲を超えた部分の数値は無視されてしまいます。
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | |
OR) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
= | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 |
x&(-1) x^0 ~~x x<<0 x>>0
また、本来は文字列に対しての関数ですが、
parseInt(x)でも整数化ができます。parseIntでは32ビット化はされませんが、一旦文字列オブジェクトに変換してから評価されるので、思いがけない結果になる場合があります(parseInt(3e+50)→3など)。
JavaScriptのNumberは浮動小数点数
落とし穴その2
他の言語では整数型としてintやlong、浮動小数点数型としてfloatやdoubleなどが用意されていますが、JavaScriptにおいては数値を表すのにNumberのみを用います。
先述のように論理演算は32ビットの整数として扱われていますが、実際はもっと大きな数も格納できます。
なぜ32ビットより大きい数を格納できるかというと、Number型の変数に入っているのは64ビット浮動小数点数だからです。
「小数」といっても、所詮は2進数で32ビットで表せる整数は完全に内包しているのです。
以下のような式で、先頭ビットが1で残り53桁が0の数が得られます。
(Math.pow(2,53)).toString(2);
しかし、それに1を足した数も同じ数になってしまいます。
(Math.pow(2,53)+1).toString(2);
本当は
64ビット浮動小数点数の表現範囲
原理を説明します。浮動小数点数では、以下の形式で数を表現しています。
k(仮数部と呼ばれる)は52ビット(表現上は53ビット)の2進数を1以上2未満になるように割った数、p(指数部)は11ビットの2進数です。
(Math.pow(2,53)+1)が正しく表記されていたら、k, pはそれぞれ以下の2進数になります。
pは充分表現範囲内に収まっているのですが、kの桁数が52を超えているので、ここで表現範囲を超えてしまい、最下位ビットが丸められてしまうのです。
整数化チェッカー
入力した式を整数化する際の挙動の違いをチェックできるプログラムを書いてみたので、 いろいろ入力してチェックしてみてください。まとめ
- Numberオブジェクトの変数には64ビット浮動小数点数が格納され、非常に大きな数は丸められる
- 論理演算をするときだけ32ビット整数として扱われる
- 整数化という用途においては論理演算を使わずにMath.floorを使うのが無難