モチベーション
Webアプリケーション等の製品のソースコードを書いている中ではビット演算なんてしないんだけど、エンジニアの嗜みとして知っておくべきだよな、と思ったので色々試してみます。
環境
とりあえずローカルのnodeで試した
% node -v v12.16.1
演算子
これに基づいて勉強
ビット論理積 (AND)
1100 & 1010 は 1000になりそう。試してみよう。
> 24 & 20 16
おお〜〜!!
ビット論理和 (OR)
1100 | 1010 は 1110になりそう。試してみよう。
> 24 | 20 28
おおー!
ビット排他的論理和 (XOR)
1100 ^ 1010 は 0110になりそう。
> 24 ^ 20 28
そろそろ感動もなくなってきたね😅
ビット否定 (NOT)
~をつけると0と1が反転するらしい。
~ 1100が 0011、つまり3ってことか?
> ~ 24 -25
な、なんだってー😅 というわけで、調べる。 Mozillaさんによれば、
JavaScript の Number 型は IEEE 754 の倍精度 64ビットバイナリ形式
であるという。(IEEE 754は仕様の名前で、「倍」精度は昔は32ビットが基本だったからそれの倍の64だから「倍」精度と言っている。) ではそのこのとき、先程の24はどのようにビットとしては表現されていたのか?指数部とか仮数部とかはちょっと複雑なので単純化すると
00001100
みたいな感じになっている。これにNOT演算すると、0011ではなく、
11110011
となる。で、これはどういう数値か?
24は00001100
でした。
では、25は1足せば良いので、00001101
ですね。では、試しに先程のNOTした後の11110011
と25である00001101
を足してみましょう
11110011 + 00001101 ---------- 100000000
となり、今は8ビット(1バイト)のテイで24を00001100
と定義していたので、100000000
の一番左の1は桁が溢れて落ちます。つまり00000000
、0となるわけです。
11110011はどういう数値か?25に加えたら0になる数値。つまり-25なのだ✌(2の補数表現で実装されているということがこれでわかる)
左シフト
a << b で2進表現の a を b ビット分だけ左にシフト(右から 0 で詰める)ができるらしい。つまり、桁が1つ増えるということは、 << 1で2倍できるということかな?
> 8 << 1 16 > 8 << 3 64
なるほど。ちなみにどれくらいの桁で溢れるんだろうか。0で埋めるんだからいつか突然0になるはず。
> 1 << 29 536870912 > 1 << 30 1073741824 > 1 << 31 -2147483648 > 1 << 32 1
ほう?29->30は普通に2倍されている。30->31は、01000...000
だったのが10000...000
のように1と0が31ビット続くようになって、2の補数なので-2147483648になったんだな。
で、なんで1 << 32で1になるの?😅
> 1 << 32 1 > 1 << 33 2 > 1 << 34 4 > 1 << 35 8
謎すぎる😅謎めいた心の中冷めた君はミステリアス♪😅 32ビットとして扱われるという前提だから、サポート外の動きだからこれ以上追っても意味ないのかな。Node.jsのソースはちらっと見たけど.ccということでC++だった。ちょっと追えそうになかった。。。C++のビット演算子をそのまま使っているのだろうか・・・
符号維持右シフト
a >> bで2進表現の a を b ビット分だけ右にシフトします。溢れたビットは破棄します。ですって。 正の数で試すと
> 4 >> 1 2 > 4 >> 2 1 > 4 >> 3 0
なるほど。負の数は?
> -4 >> 1 -2 > -4 >> 2 -1 > -4 >> 3 -1 > -4 >> 4 -1
これは算術右シフトです。つまり、-4は 11111100
みたいな感じなんすけど、これを11111110
としている、つまり右端の0を落としてその代わりに左に1を入れているんですね。シフト前の左端の数と同じ数を入れるのが算術右シフト。それと違ってとりあえず0入れちゃうのが論理右シフト。11111111
以降は、いくら右にシフトしても11111111
のままなんで、ずっと-1なんですね〜。
ゼロ埋め右シフト
👆でちょろっと触れてしまったけど、a >>> bで2進表現の a を b ビット分だけ右にシフトします。溢れたビットは破棄し、左から 0 で詰めます、との。つまり、
> 4 >>> 1 2 > 4 >>> 2 1 > 4 >>> 3 0
これは同じだけど
> -4 >>> 1 2147483646
WOW! 11111100
を01111110
にしたってイメージですね。32ビットでの最大の正の数は2147483647ですから、そこから1引いた値になっているということで納得だ。