ビット演算子 - ビット単位で演算を行う
ビット演算とは、ビット単位の論理和、論理積を求める演算のことです。ビット演算を使用する機会は少ないですが、sysopenなどC言語のライブラリを直接利用した関数を使う場合に、ビット演算を行うことがあります。またビット単位の低レベルなデータ操作を行いたい場合に使うことがあります。
ビット演算には、論理積、論理和、排他的論理和、否定があります。
演算子 | 意味 | ビット演算の例 | ビット演算の結果 |
& | 論理積 | 1100 & 1010 | 1000 |
| | 論理和 | 1100 | 1010 | 1110 |
^ | 排他的論理和 | 1100 ^ 1010 | 0110 |
~ | 否定 | ^1100 | 0011 |
2進数、16進数、10進数の対応
2進数 | 10進数 |
0001 | 01 |
0010 | 02 |
0011 | 03 |
0100 | 04 |
0101 | 05 |
0110 | 06 |
0111 | 07 |
1000 | 08 |
1001 | 09 |
1010 | 10 |
1011 | 11 |
1100 | 12 |
1101 | 13 |
1110 | 14 |
1111 | 15 |
2進数の繰り上がりと10進数
2進数 | |
00000001 | 1 |
00000010 | 2 |
00000100 | 4 |
00001000 | 8 |
00010000 | 16 |
00100000 | 32 |
01000000 | 64 |
10000000 | 128 |
10進数で2倍になることと、2進数で位がひとつ上がることが対応しています。
ビットシフト演算子
ビットシフトとは、ビットの並らびを左右にずらすことです。ビットシフトには右ビットシフトと左ビットシフトがあります。数学の視点で見ると、左ビットシフトは2倍、右ビットシフトは 2で割った商と同じです。
演算子 | 意味 |
>> | 右ビットシフト |
<< | 左ビットシフト |
ビットシフト演算子
# 左ビットシフト $num << 1; $num << 2; $num << 3; # 右ビットシフト $num >> 1; $num >> 2; $num >> 3;<< と >> が、ビットシフト演算子です。指定した数値だけ、シフト演算が行われます。
上記の2進数は10進数ではいくつになるでしょうか。位とビットの値から以下のように計算できます。
位 | 7 | 6 | 5 |
ビット | 0 | 0 | 1 |
2の7乗×0 + 2の6乗×0 + 2の5乗×1 + 2の4乗×0 + 2の3乗×0 + 2の2乗×1 + 2の1乗×0 + 2の0乗×0
すなわち
128 × 0 + 64 × 0 + 32 × 1 + 16 × 0 + 8 × 0 + 4 × 1 + 2 × 0 + 1 × 0 + = 32 + 4 = 36
左ビットシフトの意味
左ビットシフトは、値を2倍にすることに等しいです。
位 | 7 | 6 | 5 |
ビット | 0 | 0 | 1 |
左ビットシフトさせると
位 | 7 | 6 | 5 |
ビット | 0 | 1 | 0 |
計算すると
128 × 0 + 64 × 1 + 32 × 0 + 16 × 0 + 8 × 1 + 4 × 0 + 2 × 0 + 1 × 0 + = 64 + 8 = 72
で確かに2倍になります。
反対に右ビットシフトを行うと、2で割った商と同じです。
コンピュータにおけるビットの意味を理解する
ビットの意味自体がわからないという方は、こちらをご覧ください。
ビットとは、コンピュータが扱うデータの最小単位です。ビットの状態には、「立っている」か「立っていない」かの2通りしかありません。2進数で表現すると、「1」か「0」かということです。1と0並びはビット列と呼ばれます。
コンピュータの装置には、メモリと呼ばれるものがありますが、メモリとは巨大なビット列を保存する装置です。
電球のたとえ
メモリをイメージするのは最初は難しいと思いますが、電球が一直線にたくさん並んでいるものと思えばイメージしやすいと思います。たくさんの電球の並びがあって「ついている電球」と「ついていない電球」があります。
○×○×○○○×
上記のように、電球が8個あるとします。ひとつの電球で「ついている状態」と「ついていない状態」を表現できます。つまり、ひとつの電球で2通りの表現ができます。8つあれば、2の8乗で、256通りの表現ができます。
電球を用いた情報伝達
電球を使って情報を伝達してみます。AさんとBさんがいて、電球のつきかたについて次のような取り決めを行ったとしましょう。
○○○○×××× → 助けてくれ。 ×○○○○××× → 無事についた。
こうすれば、AさんとBさんは、夜の何も見えない場所、遠くて音が聞こえない場所でも、電球を使って情報をやり取りすることができます。「○○○○××××」のような単独では無意味な記号に、人間が意味を与えることで情報をやり取りできます。
電球の並びに2進数という数学的な意味を与える
電球のならびに、2進数という数学的な意味を与えます。
×××××××× → 00000000 ×××××××○ → 00000001 ○××××○×○ → 10000101
こうすることで、単なる電球の並びが、数学的な意味を持つようになります。これがビット列と呼ばれ、コンピュータの計算の基礎になります。
サンプルコード
以下は、ビット演算のサンプルと、ビットでフラグを表現するサンプルです。
use strict; use warnings; # 0b を数字の前につけると2進数で表現できます。 # %04b という書式指定で4桁左0埋めの # 2進数表現を出力できます。 print "1: 2進数1100と1010の論理積\n"; printf("%04b", 0b1100 & 0b1010); print "\n"; print "2: 2進数1100と1010の論理和\n"; printf("%04b", 0b1100 | 0b1010); print "\n"; print "3: 2進数1100と1010の排他的論理和\n"; printf("%04b", 0b1100 ^ 0b1010); print "\n"; # Perlは数値を倍精度の浮動小数点(32ビット) # で表現するので、32bitすべてのビットが # 反転する。 print "4: 2進数1100の否定\n"; printf("%04b", ~0b1100); print "\n\n"; print "5: ビット演算を用いてすべてのフラグがオンであることを表\現する。\n"; sub FLG1{ 1; } # 1を返すサブルーチン。1は2進数では、 1。 sub FLG2{ 2; } # 2を返すサブルーチン。2は2進数では、10。 sub FLG3{ 4; } # 4を返すサブルーチン。4は進数では、100。 my $all_flg_on = FLG1 | FLG2 | FLG3; printf("%03b", $all_flg_on); print "\n\n"; print "6: FLG3がオンであることを確認する。\n"; if ($all_flg_on & FLG3) { print "FLG3はオンになっています。\n"; }
printf("%04b", 0b1100 & 0b1010);
(1)2進数で数字を表現する
0bというプレフィックスを使うことで、2進数で数値を表現することができます。
(2)2進数表現で出力する
2進数で出力するには、printf関数を用いて、書式に%bを指定します。%とbの間にある04というのは、4桁表示で、満たない部分を0で埋めるということを意味するオプションです。
(3)ビット演算でフラグを表現する
sub FLG1{ 1; } # 1を返すサブルーチン。1は2進数では、 1。 sub FLG2{ 2; } # 2を返すサブルーチン。2は2進数では、10。 sub FLG3{ 4; } # 4を返すサブルーチン。4は進数では、100。 my $all_flg_on = FLG1 | FLG2 | FLG3; printf("%03b", $all_flg_on); print "\n\n"; print "6: FLG3がオンであることを確認する。\n"; if ($all_flg_on & FLG3) { print "FLG3はオンになっています。\n"; }
(3)-1 サブルーチンで定数を表現する
sub FLG1{ 1 };
こう書くと、サブルーチンが呼び出されたときに1が返却され、定数のように扱うことができます。呼び出すときは、FLG1() と書かなくても、FGG1で呼び出すことができます。
(3)-2 ビットが重ならないようにフラグの値を2の倍数にする
sub FLG1{ 1; } # 1を返すサブルーチン。1は2進数では、 1。 sub FLG2{ 2; } # 2を返すサブルーチン。2は2進数では、10。 sub FLG3{ 4; } # 4を返すサブルーチン。4は進数では、100。
2の倍数を用いることで、ビットが重ならないようにします。フラグとして用いるためには、各ビットが別々に判断できる必要があります。
(3)-3 すべてのフラグがオンになっているという表現
my $all_flg_on = FLG1 | FLG2 | FLG3;
オンにしたいビットを論理和で結び付けます。
(3)-4 フラグがオンであることを確認する
if ($all_flg_on & FLG3) { # ... }
オンであるかを確認したいフラグとの論理積を取ることで、フラグがオンになっているかを確認できます。
(3)-5 基本的にビット演算は避ける
基本的にビット演算を使うようなプログラムは書きません。ビット演算の必要が生じるのはC言語のライブラリを直接ラップしたPerlの関数を呼び出すときくらいです。
ビット演算の意義を書くと、メモリの節約と処理の高速化です。1ビットで判定できるので、少ないビットで多くの情報を表現できます。また、1条件につき1ビット判定するだけなので処理は高速になります。
以下は、ビットシフト演算のサンプルです。
use strict; use warnings; my $num = 8; print "\$num = $num\n\n"; print "1: 左ビットシフト\n"; # 2倍と同じ print "1ビット左シフト: " . $num << 1 . "\n"; # 2の2乗倍と同じ print "2ビット左シフト: " . $num << 2 . "\n"; # 2の3乗倍と同じ print "3ビット左シフト: " . $num << 3 . "\n"; print "\n"; print "2: 右ビットシフト\n"; # 2で割った商と同じ print "1ビット右シフト: " . $num >> 1 . "\n"; # 2の2乗で割った商と同じ print "2ビット右シフト: " . $num >> 2 . "\n"; # 2の3乗で割った商と同じ print "3ビット右シフト: " . $num >> 3 . "\n"; print "\n";
2進数から10進数への変換
00100100