がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

ビット演算の説明と実例

某所でこんな質問をもらいまして。
うん確かに「ビット演算がわからない」と、それ以上に「なにに使うのかがわからない」って話はよく耳に目にするところなので。
せっかくなんで、Blogで返答をいたしますw


質問は、これ。

ビット演算子のこの部分が


$a = 0xFF;
$b = 0xF0;
$c = $a & $b;
var_dump($c);
echo sprintf("%08b\n",$c); //2進数で出力11110000


よくわかりません。
この11110000って、16進数だとF0、10進数に戻すと240になります。なので、直接F0を2進数にすればいいのに。。。(2進数にするやりかたわからないのですが^^;)
$a & $bは教科書には$a及び$bの両方にセットされているビットをセットするって書いてあります。なんですかこれ^^;
ビット演算子についてあまり理解ができていないからかもしれませんが、、、
その$a & $bと、実際にどういった場面でビット演算子を使うのか教えていただけませんでしょうか。

ん…まずは「演算子」を念のために。
1と0の世界において、andとorとxorは、こんな風になります。


1 and 1 → 1
0 and 1 → 0
1 and 0 → 0
0 and 0 → 0


1 or 1 → 1
0 or 1 → 1
1 or 0 → 1
0 or 0 → 0


1 xor 1 → 0
0 xor 1 → 1
1 xor 0 → 1
0 xor 0 → 0


andはかけ算みたいな感じで「どっちかが0なら0」なので、論理積なんて言い方もします。つまり「右手のダイヤも左手のプラチナも全部よこせ」ってな感じですねw
orは足し算みたいな感じで「どっちかが1なら1」なので、論理和なんて言い方もします。「右もいいけど左もいいから、どっちでもいいわ(両方もありだけどね)」って感じ。
xorは排他的論理和って言われますが、まぁつまりは天の邪鬼。「2つの値が異なれば1、一緒なら0」。そうですねぇさしずめ「他の人と同じなんて嫌!!」ってな感じでしょうか。



で、ビット演算の、まずは「結果」から。

$a = 0xFF;
$b = 0xF0;
$c = $a & $b;
var_dump($c);
echo sprintf("%08b\n",$c); //2進数で出力11110000

ん…先にprintf周りから。

echo sprintf("%08b\n",$c); //2進数で出力11110000


は、一般的には以下のように書くことが多いですね。

printf("%08b\n",$c); //2進数で出力11110000


printfについては、とりあえず下記参照。
http://www.php.net/manual/ja/function.printf.php
フォーマットはまぁ色々あるので( http://www.php.net/manual/ja/function.sprintf.php )、ここでは「%b」と「%08b」について覚えておきましょう。

b - 引数を整数として扱い、 2 進数として表現します。

とあるので、%bで「数値を2進数として表現」。
ただ、それだけだと、例えば2進数で「00000100」を「100」て出しちゃうので。
ん…サンプルコード。

printf("%b\n", 4);

慣習的に2進数って大抵「4桁とか8桁とか、先頭の0を省略せずに書く」のが一般的なので、08ってのをいれて「先頭が0なら0でいいからちゃんと0だして8桁で表示して」って感じにしておくです。

printf("%08b\n", 4);


次。

$a = 0xFF;
$b = 0xF0;

は、単純に16進数で$aと$bに値を突っ込んでる。
計算が見やすいように書いておくと、こんな風になる(実際にプログラムとして書くとエラーになるから注意)。

$a = 11111111(二進数);
$b = 11110000(二進数);


で、質問のコードでは&、つまり「ビット積(ビット演算子で、ビットごとにandする)」が指定されているので。
上下に書くとこんな感じ。


11111111:$aの内容
11110000:$bの内容


これのand。
andは上述の通り「両方1なら1」「それ以外は0」なので。
したがって、こうなる。


11111111:$aの内容
11110000:$bの内容
11110000:$aと$bをandした内容


なので、$cには11110000(二進数)が入る。
んで。「$a および $b の両方にセットされているビットがセットされます」「$a または $b のどちらかにセットされているビットがセットされます」はわかりにくいので。
これは「$aと$bを、ビットごとにandします」「$aと$bを、ビットごとにorします」って読み替えてOK。orしたりandしたりした結果は、左辺の変数にぶちこまれる感じ。


ここまではよいかし?


んで、実用例。
基本的には、フラグ、って考え方があって。
orは「フラグを立てるとき」に使って、andは「特定のフラグが立っているかどうかを見るとき」または「強制的に特定のフラグを寝かせたいとき」に使うざんす。
xorは「bit反転」でよく使うんだけど、今回はオミット。


ん…まずは基礎知識。
「フラグ」っていう、これは概念があって、プログラムではよく使われます(時点はエロゲー、ギャルゲー、その他ゲームなどでよく用いられますw)。
いわゆる「フラグが、フラグが立った!」っていうアレですね。


んで。100%ではないですが、フラグ用の値にはよく整数が用いられます。
昔々は「1bitのメモリにも88人の神様が」とか言われている時代もあったので(それはお米)(いやでも実際、それくらい貴重でした)。
出来るだけ無駄を省くために、bitをフラグに見立てていた時期があったんですね。いやまぁ実際、なれると計算楽だし。


ん…わかりやすく、ここでゲームのフラグを仮想してみましょう。
2進数で0000 0000の8桁を想像してください。
左から順番に、以下の意味をもつと仮定します。
・マリさんとのデートしたフラグ
・マリさんとデートが可能フラグ
・マリさんとおしゃべりをしたフラグ
・マリさんにアプローチをしたフラグ
・ミエさんとのデートしたフラグ
・ミエさんとデートが可能フラグ
・ミエさんとおしゃべりをしたフラグ
・ミエさんにアプローチをしたフラグ


ある人のフラグが00010001(16進数で11、10進数で17)だとすると、この人は「マリさんとミエさんにアプローチをしている」状態であることがわかります。
これをプログラム的に書くと、こうなります。なお、フラグ情報は「$gflg」の中に入っていると仮定します。
また、プログラム理解が楽なように、数値は二進数で書いてます。ので、そのまんまPHPコードとして動かすとエラーが出るんで注意。

// ミエさんへのアプローチチェック
if (0 !== (00000001(二進数) & $gflg)) {
  echo "あんた、ミエさんにアプローチしたね?\n";
}

// マリさんへのアプローチチェック
if (0 !== (00010000(二進数) & $gflg)) {
  echo "あんた、マリさんにアプローチしたね?\n";
}

こんな感じ。
つまり「特定のbit(フラグ)がたっているかどうか」をみたい場合、そこだけbitを立てた値とandしてみて、0以外が帰ってくれば「あぁきっとbitがたってるんだろうなぁ」って見当がつくわけですね。


で、何かイベントがあってもし「マリさんとおしゃべりをしたフラグ」を立てたい場合。
「マリさんとおしゃべりをしたフラグ」そのものは「00100000」なんで、これは、orを使います。

// おしゃべりしたらしい
$gflg = $gflg | 00100000(二進数);

これで、gflgの「マリさんとおしゃべりをしたフラグ」が必ず1になります。
単純に「+で足しても一緒じゃん」とか思ってしまいがちですが、「すでに"マリさんとおしゃべりをしたフラグ"がたっている」場合に予期しない動きになるので、この手のflg操作にはor演算子(つまり |)を使うのが一般的です。


…っていう、なんか微妙に一般的じゃない説明を書いてみましたw
質問とかあったらよろしくです〜 ノ