JavaScriptのビット演算な罠

 プログラム
 公開日:2016年1月22日

 先日、
国コードから割り当て済みIPv4を抽出、あるいはその逆を行う
といった感じのアプリというかツールっぽいのを作ったのですが、
その際にビット演算でちょっとハマったので、
書いてみたいと思います。

 こちらにあるように、
JavaScriptでビット演算を行うと数値は32ビットな値として扱われます。

 あらためて確認すると、
32ビットというのは0または1の2進数なビットが32個連なったもので、
2の32乗個、すなわち4294967296個の組み合わせが表現できます。
10進数の正の整数として表現すれば、
0~4294967295の最大10ケタとなります。
また16進数な表記では、0x0~0xffffffffの最大8ケタとなります。

 ところでJavaScriptにおける数値は、
64ビット倍精度浮動小数点数の形式である
IEEE754にしたがって実装されています。
仮数部の精度としては53ビットありますので、
10進数の正の整数としては最大16ケタまで表現できることになります。

 ということで実際に確認してみます。
意図的に選んだ16進数表記な数値を console.log()してみました。
以下のように、期待した通りの結果となっています。

 次にビット演算をやってみます。
変化が起きないようにゼロとビット論理和してみました。
以下のような結果となりました!

 興味深い結果となってますが、
仕様通りに32ビットに収まる値となっています。
32ビットを超える部分はカットされ、2の補数な表現となっています。
つまり符号あり32ビットな整数として扱われています。
値としては、-2147483648~2147483647を表現できることになります。

 で、ここから私がハマった話に入ります(^_^;)
IPv4では表現できるアドレス範囲は、
0.0.0.0~255.255.255.255となっています。
16進数で表記すれば0x00000000~0xffffffffとなり、
ちょうど32ビットに収まります。
ソートする必要があったので、
文字列表記なアドレスを全て数値に変換して格納するようにしました。
127.255.255.255までは問題ないのですが、
128.0.0.0以降になるとソートが期待した通りにならなくなります。
自分の中では符号なし32ビットだったのですが、
実際は符号あり32ビットなんです(^_^;)
ビット演算すると32ビット数値として扱われることは知っていたのですが、
符号までは意識していませんでした。
というか
32ビットぶんを目一杯使うことってあまり無いし・・・。
ビットなフラグ用途なことが多いし・・・。

 愚痴っていてもしょうがありません(^_^;)
1)ビット演算を使わずに変換する
2)負数になったら正規化する
のどちらかにすれば解決するはずですが、
なんかもっとスマートな方法はないかと調べてみたら、
これを見つけました。
こんな手があったのね!

 ときに、
JavaScriptのビットな右シフトには、
>>>>> があります。
2つあることをすっかり忘れてました(^_^;)
前者は算術右シフト、後者は論理右シフトです。
左シフトは結果が同じなので区別してないだけです。
端的に言うと、
符号あり数値として扱っているなら算術右シフトを、
符号なし数値として扱っているなら論理右シフトを
使うことになります。

 重要なことは >>> すると、
数値は符号なし32ビットな値として扱われます。
符号あり32ビットを符号なしに変換できることになります。
実際にやってみると以下のようになります!

 JavaScriptに型宣言はありませんが、
number | 0 とか number >>> 0とか書けば、
符号あり32ビット値や符号なし32ビット値を暗黙的に示すことができます。
浮動小数点数から32ビットな整数へ変換するのにも利用できます。
JavaScriptエンジンに対して最適化を促すことができるのかも。
asm.jsな技術とかはこういったのを利用して下位互換性を図っているようです。

コメント投稿