サンダーボルト

相手モンスターを全て破壊する。

ビット演算子を色々試してみる

モチベーション

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! 1111110001111110にしたってイメージですね。32ビットでの最大の正の数は2147483647ですから、そこから1引いた値になっているということで納得だ。