ビット演算講義

Last Updated:

スクリプトリファレンスをななめ読みしていたら、and, or, xor, notというよく意味の分からない命令があるぞ?と思った経験がある方がいると思います。これらの命令は、ビット演算(論理演算)と呼ばれるコンピュータ独特の演算子です。あまり日常との接点もないし、マニュアルの記述も薄っぺらなのでさほど重要とも思わず流してしまった方も多いのではないでしょうか。

しかしビット演算は、要点を押さえるとスクリプトを書くうえでとても便利に使えるものです。

コンピュータの仕組みと2進数

私たちは日常、数を10進法で取り扱っています。0から9の10種類の数字を使って、9の次の数は桁が増えて10になりますね。しかし、コンピューターは2進数の計算に優れています。0と1の2種類の数字を使って数を表すのが2進法です。 コンピュータだって10進数の計算をしているようにも見えますが、コンピュータはすべての数を2進法に読み替えたうえで計算処理をし、10進法に書き直して出力してくれているのです。要は、使う文字の数が少なくて済むので仕組みが簡単にできたからこうなっているんですね。

それで、「ビット」とは、2進法で数を表した時の桁とか位のことをいう言葉です。ビット演算は、2進数を各桁ごとに一定の規則で演算をするものです。ビット演算は、数をコンピュータが見ているままの姿で取り扱える(さらに言うと、繰上りがない)ので、コンピュータが最も高速に処理できる演算なのです。

それぞれのビット演算の説明に入る前に、簡単に10進数を2進法表記にする(のと、その逆)の練習をしておきましょう。10進数を書いているときは黒字のままですが、2進数を書いているときには文字をにすることにしましょう。

0と1は、どちらで表記しても同じです。10進数の2は、2進数では2という数字が使えないので、位が上がって10になります。3は11です。10は1010ですね。2進数ですから右から一の位、二の位、四の位、八の位…になっているのがポイントです。

逆に行きましょう。2進数の1101は、10進数に直すと13です。 8×1 + 4×1 + 2×0 + 1×1 = 13 ですね。もうお分かりでしょうか。

ちなみに、2進と10進の計算には、Windows付属の電卓をプログラマ電卓モードにしたものが便利です。n進法の表示切り替えがF5-F8のショートカットキーでできるのがいいです。(ソロバンでもできそうですが)

NOT演算

ビット演算の中でいちばん単純なのがNOT演算です。2進数の各桁を見て、0だったら1に置き換えて1だったら0に置き換えます。一番大きな桁で1が出てきたら、それ以上の桁には0が入っているということに(コンピュータ的には)なっていますが、これらの「無駄に並んでいる」0は演算の対象外になります。

つまり、 not(13)=not(1101)=0010=2、 not(10)=01=1、 not(1)=0です。ただし、not(0)=1となります。

基本的には、1か0(ONかOFF)の値を持つフラグを、notにかけてひっくり返すという用途に使います。

// ヘッドライトをスイッチするメソッドの例
Var flg
GetHeadlight flg
not flg
SetHeadlight flg

AND演算

and, or, xor演算は、2つの数が関係してきます。and演算は、2数の同じ桁を比べたときに、数字が両方とも1ならば1、そうでなければ0になります。

互いに(二進法で)1桁の場合は、 (1,1)→1 ですが、それ以外は (1,0), (0,1), (0,0)→0 となるのがand演算です。掛け算ぽいですね。

桁が増えると、(1010, 1100)→(1110)のようになります。

OR演算

or演算は、2数の同じ桁を比べたときに、数字がどちらか一方でも1だったら1になります。両方0だったときのみ0になります。

(1,1), (1,0), (0,1)→1、 (0,0)→0です。足し算ぽい…ですが違う部分もありますね。

and演算とor演算の使い道は、後ほど。どちらも単体だとあまり使えません。

XOR演算

エックスオアと読むんだそうです。

xor演算は、2数の同じ桁を比べたときに、数字が互いに等しいと0、異なった時に1になります。(1,0), (0,1)→1, (1,1), (0,0)→0ということです。これも足し算ぽいですね。繰上りを無視する足し算というのがいいでしょうか。

このxor演算、単体でも使えるスグレモノです。「xorするたびに(0でない2つの異なる)数を切り替えたい」ときに使えます。たとえば、xor x cするたびにxに1,2,1,2,...となってほしいとします。1=01、2=10なので、一の位と二の位が両方入れ替わりますね。cには入れ替えたい桁を1にした数を選びます。ここではc=11=2です。

するとxor(1,3)=xor(01,11)=10=2、  xor(2,3)=xor(10, 11)=01=1とうまく交互に出てきますね。

VRM的には、ホームドアのキー制御に使えると思います。(ステータスの設定値が1と2でしたね。)メソッド内で、xor doorstatus 3としてからSetCrossingStatus的なものをすればよいわけです。

AND, OR演算の利用[難易度★★★★~★★★★★]

andもorも単独で使うことはほとんどないですが、「ただの整数をたくさんのフラグの並びとみなす」使い方をするときに、一つ一つのフラグの値を出し入れするのに使えます。

類似したフラグが何個かあったときに、イチイチ変数を定義するのはメモリがもったいないですね。そこで、それぞれのフラグの0と1を並べた2進数を、一つの変数にまとめて記憶しちゃいましょう。

// 4本のフラグを一つの変数flagで扱う例
// フラグを立てるとき(1をセット)
or flag 2  // 2=0010

// 特定のフラグを変更するとき(0と1を入れ替え)
xor flag 2  // 2=0010 右から2番目のフラグのを変更

// 特定のフラグを取り出したいとき
mov result flag  // flagの中身が残るようresultにコピー
and result 4  // 4=0100 右から3番目のフラグを取り出し
if result
    // flagの右から3番目に1が立っていたときにここが実行されることになる
endif

// 特定のフラグを畳むとき(0にする)
and flag 13 // 1101 右から2番目のフラグが0になる

すこし具体的な例を取り扱ってみましょう。大宮駅上り方の出発信号を操作したい、とします。

大宮駅上り方面上野方配線図(抜粋)

大宮駅から上り方に出発する配線のみを抜粋しました。右側が上野方です。うまい配線でありまして、いろんなホームから各線に同時発着できますね。実際には、東(東北線)と高(高崎線)はさらに右側で合流しますが、深く考えないことにしましょう。さて、各ホームの出発信号機を操作するにあたって重要なのが、同時発車ができる経路とできない経路を判定することです。ここで、「フラグを一つの整数変数に並べる」手法を使ってみます。

配線図を色分けし、区間に番号(1-6)を付けました。変数lockを用意して、2進表記したときに右から順に、各区間のロック状態(1=ロック、0=フリー)を保持することにします。lock=000001は、(1)の区間(だけ)がロックされていることを表し、ロック中はあとからその区間を通過するような進路構成を禁止することにします。

まず始めは、どの列車も出発中でなく、どの区間もフリーであるとします。このときlock=000000です。ここで、4番線から東北線[東]に列車を発車させるものとします。区間(2)(1)を通過する必要があるので、その区間がフリーであることを確認してからポイントを切り替えて進路を構成し、その区間をロックして安全に通過できることを保証します。

// 4番から[東]に発車
mov check lock
and check 3 // 000011 (1)と(2)を取り出す
ifzero check
    // ここでcheck=0ならもとのlockの(1)と(2)がフリーだったことになる
    // ポイント操作をここに書く
    or lock 3 // 000011 (1)と(2)に1を立ててロック
else
    // 進路構成失敗
endif

ひとまずは進路が構成され、現在lock=000011になりました。

次に、3番線から貨物線[貨]に湘南新宿ラインを発車させたいとします。通過する進路は(1)(2)(4)(5)です。そこで and check 27 //011011 をしますと、もともと(1)(2)に1が立っていたのでcheckが000011になりそのあとのifzeroが成立しません。よって進路を構成できず、3番線の出発信号機は開通しないことになります。

しかし、6番線から[貨]に対する出発は、and check 52 //110100が0となるので出発が許可されます。

出発が終わってフリーになった区間のフラグは、xorかandで畳んでやります。

そもそもこの処理をどこに書くか、には検討の余地がありそうですが、かなりスマートなコードで複雑な条件判断ができました。(ビット演算を使わないとなると、多重のif文を書くことになり死にます。後から編集するのも大変。)

まとめ

このように、ビット演算をうまく使うと複雑なフラグ管理や条件判断を簡単に書くことができる場合があります。デメリットというか注意点は、2進として意味を持つ多重フラグ変数の各桁が、なんのフラグだったかをどこかにメモっておかないとこれも死にます。

掲載したベン図はWikimedia Commonsからパブリックドメインのものを引用した。