eraシリーズ改造/バリアント開発の覚え書き

ビットマスク


ビット演算のもっとも典型的な利用法のひとつで、ここで取り上げるまでもないくらいありふれているものだが、便利には違いないので書いておく。

8ビットの数値は、8桁の二進数で表現できる

00000000 ;=> 0
00000001 ;=> 1
00000010 ;=> 2
00000011 ;=> 3
00000100 ;=> 4
00001000 ;=> 8
00010000 ;=> 16
00100000 ;=> 32
01000000 ;=> 64
10000000 ;=> 128
11111111 ;=> 255

という感じになるのだが、実のところ上記のような10進数のある数を2進数で表記するとこうなる、という程度のことは、ビット演算をするにあたってはあまり意識する必要がない。しいていえば「1桁左へずれると2倍になる」「8bitの最大値が255である」という程度のことだが、これはビットシフトで2のn乗を見てもらうほうがいいだろう。
もっとほかのことが重要だ。ビット演算のときに考えるべきことは、ビット単位でデータを取り扱うと何がどう便利なのか、ということだ。

ところで、各要素が10種類の値であらわされる配列があったとする。

A:0 = 1
A:1 = 3
A:2 = 8

こんな感じに、0〜9までの値を配列の各要素に格納するわけだ。なるほど配列は便利だが、しかしながら、eraでは引数として渡すときに大変な苦労を強いられる。文字列として連結してしまうとか、バイナリにパックするだとか、いろいろ方法はあるのだが、とにかく面倒なことになる。
だが、配列の要素ひとつひとつが最大10種類の値までしか持ち得ないのであれば、そもそも配列にする必要すらない。

A = 831

あとは各桁の値を求めればいい。こういうときはLOG10を使うと便利である。

では次に、各要素が2種類の値であらわされる配列があったとする。具体的には、フラグのON/OFFの羅列であるとか、真偽値の配列なんかがそうだ。
これも同じように、配列で持たずとも数字の各桁に値を入れれば、ひとつの変数として取り扱うことができる。

A = 10110110

こういう数字、どこかで見たような?と思わなかった人は、もう一度この記事を頭から読み直していただくとして、そう、各桁0と1の二種類しかないのだから、二進数で表現してしまえばいいわけである。

A = 0b10110110

用意するのは簡単だが、各桁の値を取得するにはどうしたらいいだろうか。
そこで使うのが本題のビットマスクである。
マスクというのは、覆いのことである。取り出したいデータ以外を覆ってやることで、目的のデータだけを取得することができる、というわけだ。
ビット演算は筆算の表記法が分かりやすいので、そのように記したものをまずは見てもらおう。
&演算子は、ビット毎に、互いに1であれば1を、そうでなければ0を返す演算子である。

  10110110
& 00000001
= 00000000

&演算をビット積を求めるとも言うが、要するにビット単位の掛け算である。値は0と1の二種類なので、1同士でなければ0になる。掛け算といってしまうと、ちょっと語弊があるのだが、ビット単位の掛け算ということで、同じ桁同士でだけ掛け算すると思えばいい。そう、ビット演算では各桁が独立しているので、ある桁がほかの桁に影響することはない。関係ない桁をマスクしてしまえば、欲しい桁だけが取れるはずだ。
右から6桁目を取得しようと思ったら、右から6桁目だけが1である数字とのビット積を求めればよい。取得したい桁が0ならば、すべての桁が0になるから、計算結果は0になる。ビット積が0と等しいならOFF、0以外ならON、というふうに判断できるだろう。

  10110110
& 00100000
= 00100000

両方1なので1になる。ということでONだと分かる。

A = 0b10110110

SIF A & 0b00000001 ; 右から1桁目がONのとき
  :
SIF A & 0b00100000 ; 右から6桁目がONのとき
  :

ここまで書いておいてなんだが、EmueraにはGETBITという命令が組み込まれている。GETBITを使えば、ビット演算を意識せずに各桁にアクセスできるので、これを活用するのがいいだろう。

さて、GETBITがあるのになんでこんな話を書いたの?と思ったかもしれない。
今の手が使えるのは、各要素が0と1の二種類の値しか持たない配列をひとつの変数にまとめる場合。逆に、各要素が3種類以上の値を持つ場合はどうすればいいか? もはやビット単位で値を保持することはできないから、そうなると10進数を使おうか……。
しかし10進数の場合は各桁を分解するのがビット単位のときに比べて面倒が多い。コンピュータは基本的に2進数の世界なので、10進数とはもともと相性が悪い。各桁をビット単位に分割できる数ならビット演算できるのになあ。
待てよ? 2進数、10進数以外にもよく使う表記があるではないか。そうそう、色を表記するためによく使ってるあいつ、16進数だ。

0x04 ;=> 4
0x0f ;=> 15
0x10 ;=> 16
0x40 ;=> 64
0xff ;=> 255

この0xffという数字が肝である。
これは、0b11111111と同じ値だ。で、0x0fは0b00001111と同じだし、0xf0は0b11110000と同じである。0xfは、0b1111と同じということになる。
2進数8桁の数を16進数2桁の数とみなせば、16種類の値を取り得る要素を2個持つ配列をひとつの変数で表現できそうだ。では、16進数2桁の数の1桁目の値を求めるにはどうすればいいか? 2進数4桁が16進の1桁に対応するわけだから、2進数4桁をひとまとまりとして考えればいい。つまり、こういうことである。

  00100100
& 00001111
= 00000100

上位4桁を求めたければ、こうする。

  00100100
& 11110000
= 00100000

が、0b00100000は欲しい値ではない。欲しいのは0b0010だ。これは困る。
そういうときは、あらかじめビットシフトで欲しい桁を下位4桁にそろえておく。上位に余分なデータがあるかもしれないが、ビットマスクで取り除くので問題ない。

  110000100100
      ↓
      11000010
     ↓
      11000010
    & 00001111
    = 00000010

めでたしめでたし。
これをコードで書くとこうなる。

A = 0x0c24 ; 0b110000100100
B = A >> 4 & 0x0f ; 4桁右にずらしてから0x0f(=0b1111)とのビット積を求める
;=> 0010

0x1111が0x0fと同じで、16進数1桁は2進数4桁に対応する、ということが分かれば、なぜ4bit右にシフトするのか、とか、0x0fとのビット積を求めればいいのか、ということが分かるんじゃないだろうか。
実際にこれを応用したものが、16進数カラーコードからRGB値を取得したいに書いてある。より実用的な例になっているので、こちらも見てもらえれば幸い。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

リンク

漠々ト、獏
eramaker/eramaker2の開発元の公式サイト。

Emuera - emurator of eramaker
C#で書かれたeramakerのエミュレータ「Emuera」のプロジェクトページ。

eraシリーズを語るスレ まとめ
eraシリーズ全般のまとめ。バリアント情報、改造情報など。

eratoho まとめ
eramakerのバリアント「eratoho」のまとめ。

era板
eraシリーズについての掲示板。

サブページ

Rubiera
Bitbucket上のRubieraプロジェクトページ。Rubieraのソースコードのダウンロードはここで。

管理人/副管理人のみ編集できます