gallu’s blog

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

いにしえ(かもしれない)技法:bit演算変

bit演算を使ったプログラミング、割と「知られてないのでは?」という感じがあったので。
ここでは少し抽象的な部分が混ざってしまうのだけど、まぁなんかの一助になるかなぁ、くらいの感じで、めも。

大まかには
・処理が「A」「B」「A+B」みたいな感じの時の処理の共通化
・いわゆるフラグ的に on/off 出来るステータスがあるようなもの
の時に便利だったりします。

前提

まず、2進数はよいよね?
2進数は、各桁が「bit」って言い方をします。
んで、1だと「立ってる」、0だと「寝てる/立ってない/落ちてる」とかって言い方をする事が、まぁ。
1の「立ってる」は割と共通な気がするなぁ。0はあちこち方言がありそうだけど。

んで。論理演算のANDは「あるbitが、立っているか寝ているか」を判定するのに割と便利につかえます。

例。
変数 $x*1の1桁目(2^0)が立っているか寝ているかを判定する場合は

if (0 !== ($x & 0b0001)) {
    // 立ってる
} else {
    // 寝てる
}

って判定が出来ます。まぁもうちょっと省略して

if ($x & 0b0001) {
    // 立ってる
} else {
    // 寝てる
}

って書く事が多いように思いますが。

同じように、2桁目(2^1)が立っているかどうかは

if ($x & 0b0010) {

3桁目(2^2)なら

if ($x & 0b0100) {

で判定が出来ます。
とりあえずわかりやすく二進数表記で書いてますが、実際には「1、2、4」といった、十進数で書いている事もちょいちょい。

これ自体は、2進数とbit演算を知っていれば「知識としては知っている」内容だと思うのですが、これを上手く使うと、プログラムが楽に出来ます。
いやどちらかというと「いにしえから伝わる技法」だと思うのですが、割と昨今でも普通に使う事が多々あるので。

ちなみに、こーゆーのを「マスクビット」とか「ビットマスク値」とかいう言い方をする事があるので、知っておくと、ググる時に楽……かも。

処理が「A」「B」「A+B」みたいな感じの時の処理の共通化

まぁ今回の元ネタがこっちなので、先にこっちで(笑
入ってくるパラメタによって「Aのみ」「Bのみ」「AとBの処理」をやる場合。
一番ざっくりと書くと

if (パラメタ==A) {
    パラメタAの処理;
    パラメタAの処理;
    パラメタAの処理;
} else if (パラメタ==B) {
    パラメタBの処理;
    パラメタBの処理;
} else if (パラメタ==A+B) {
    パラメタAの処理;
    パラメタAの処理;
    パラメタAの処理;
    パラメタBの処理;
    パラメタBの処理;
}

となるか、と思います。

で、少し共通化すると

function func_A() {
    パラメタAの処理;
    パラメタAの処理;
    パラメタAの処理;
}
function func_B() {
    パラメタBの処理;
    パラメタBの処理;
}

//
if (パラメタ==A) {
    func_A();
} else if (パラメタ==B) {
    func_B();
} else if (パラメタ==A+B) {
    func_A();
    func_B();
}

ってなるか、と思うのですが。

これを、bit演算を使うと

$type = 0;
if (パラメタ==A) {
    $type = 1; // 0b0001
} else if (パラメタ==B) {
    $type = 2; // 0b0010
} else if (パラメタ==A+B) {
    $type = 3; // 0b0011
}
//
if ($type & 1) { // 0b0001
    パラメタAの処理;
    パラメタAの処理;
    パラメタAの処理;
}
if ($type & 2) { // 0b0010
    パラメタBの処理;
    パラメタBの処理;
}

って書く事ができます。

いやまぁ関数(メソッド)に切り出してもよいんですが。
「微妙に切り出しにくい(面倒な)処理」とかで、かつ「ここでしか使わない」ようなロジックの場合、こんな風に書くと
・重複した記述がいらない
・(慣れてれば)見やすい
ので、すっきり書けますよ、的な感じになります。

いわゆるフラグ的に on/off 出来るステータスがあるようなもの

この辺は、PHPの関数でも割とおなじみなように思うのですが。
ふと思い出しやすい所で、 file_put_contents( https://www.php.net/manual/ja/function.file-put-contents.php ) を例に。

第三引数のflagsが、まさにこのやり方をやっています。

<?php

var_dump(FILE_USE_INCLUDE_PATH);
var_dump(FILE_APPEND);
var_dump(LOCK_EX);

int(1)
int(8)
int(2)

4がいませんが、まぁ2進数にすると
0b0001
0b1000
0b0010
で、ちょうどそれぞれ「1つだけbitが立っている」値になっています。

なので、例えば「FILE_APPENDかつLOCK_EXにしたい」場合は、論理和(or)を使って「 FILE_APPEND | LOCK_EX 」とやると、$flgには10(0b1010)が渡るので。
あとは、プログラムで

if ($flg & 0b1000) {
    // FILE_APPEND用の処理
}
if ($flg & 0b0010) {
    // LOCK_EX用の処理
}

って書き方をするから便利なんですね。

……と、そういえば error_reporting( https://www.php.net/manual/ja/function.error-reporting.php )もこれをがっつり使ってましたねぇ。
こっちは https://www.php.net/manual/ja/errorfunc.constants.php で(とりあえず現在のバージョンの時の)「各定数の数値」まで書いてあるので、見てみるとよいか、と。
値を2進数にしてみるとわかるのですが、いずれも「1箇所だけ1、残りは0になってる2進数」になっているかと思います。
だから、error_reportingの値は、主にor(|)をつかって「複数指定」が出来るんですね*2

この辺、多分今でも(そして多分、今後も)なにげに出てくると思われるので、知っていると便利かなぁ、と。

*1:一応、PHPerだからねぇおいちゃんw

*2: 「bit反転(~)+and(&)による一部bit(主にNotice)寝かせ」は、邪悪な行為なのでおいちゃんのコズムではリアリティにより否定されます