目次 | 前の話題 | 次の話題

2/16進数計算について

1998年10月17日

このシーナリつれづれノートを開始してもう1年が経過した。正直いって 1年も持つとは思っていなかった。(って、最近更新をさぼりまくっていて こんなことをいうのもずうずうしいが、、、)

さて、今回は 2進数、16 進数の計算について説明しようと思う。多少高度な シーナリ作成をしようとすると内部変数を使わないといけないのだが、このとき に2進数、16進数の計算が必要になるのである。


2進数とは?

私たちが普通使っている数値は10進数である。(0,1,2.. と数えていき、9 の次 は1つ繰り上がりを起こして 10 になる)ところが、10進数というのはコンピュー タにとっては取り扱いにくい。コンピュータの内部では全ての処理を「オン」と 「オフ」の2つの状態で行うからだ。

そのため、コンピュータの内部では数値は全て「2進数」で処理されている。 2進数では "0" と "1" の2つの数字しか使わない。

2進数では数値が2つ増えるたびに1回繰り上がりをおこす。つまり、0, 1 の 次の数値は 10 となる(これは"ジュウ" と読まずに "イチゼロ" と読む)下に 10進数と2進数の対応表を書いておいた。

10進数     2進数
------------------
  0          0
  1          1
  2         10
  3         11
  4        100
  5        101
  6        110
  7        111
  8       1000
  9       1001
 10       1010
 11       1011
 12       1100
 13       1101
 14       1110
 15       1111
 16      10000
 17      10001
 18      10010
 19      10011
 20      10100
 ...      ...
さて、これだけだと "10110" などと書かれたときに10進数の "イチマンヒャクジュウ"なのか、2進数の”イチゼロイチイチゼロ”なのか 区別できない。このようなときは2進数をあらわす "B" を末尾につけて "10110B" というように書く。

2進数の1つずつの "1", "0" を"ビット(bit)"と呼ぶ。ビットが 8つ集まったものを "バイト(byte)" とか "オクテット(octet)"と呼ぶ。

1バイトは、ビットが8つあるので 2 の 8 乗 = 256 通りの数値を 表現できる。つまり、1バイトで 0 から 255 までの数値を表現できる。

さて、普通ならここで2進数と10進数の演習問題などをだすところであ るが、そんな七面倒なことはしない。なぜなら、Windows に付属の電卓で 2/10/16進の計算ができるからだ。これをするには、Windows の電卓を起 動して、「電卓の種類」を「関数電卓」にするだけでいい。こうすると 電卓に 16進, 10進, 8進, 2進の切り替えボタンがでる。ここを押すだけ で変換ができる。

これでしばらく遊んでみてほしい。


16進数

さて、上の表をみてもわかる通り、同じ数字を10進数と2進数で表現す ると、2進数のほうは桁数が多くなる。例えば10進数で 1,000 という 数値は2進数では 1111101000B となり、人間にとっては 読むのも書くのも大変である。

そこで、通常は2進数のかわり16進数を使って表記を行うことが多い。 16進数では数値が16までいくと1つ繰り上がりをおこす。ただ、16 進数だと 0 から 9 までの数字だけでは表現できないので、アルファベッ トの 'A' から 'F' までを併用する。

10進数     2進数      16進数
------------------------------
  0          0         0
  1          1         1
  2         10         2
  3         11         3
  4        100         4
  5        101         5
  6        110         6
  7        111         7
  8       1000         8 
  9       1001         9  
 10       1010         A
 11       1011         B
 12       1100         C
 13       1101         D
 14       1110         E
 15       1111         F
 16      10000        10
 17      10001        11
 18      10010        12
 19      10011        13
 20      10100        14
 ...      ...
16進数は2進数と非常に相性が良い。なぜなら、16進数の1桁が 2進数の4桁(4ビット)にぴったり一致するのである。(4ビットの 数値は 2 の4乗 = 16通りの数値を表せる)

例えば、2進数の "111010010011B" は上の表を使って

        1110 1001 0011
         |    |    |
         V    V    V
         E    9    3
つまり、16進数の "E93" に簡単に変換できる。

なお、16進数を表記する場合は後に 'H' をつける。また、先頭の数値が 'A'から 'F' の場合はさらに前に '0' をつける。例えば "0E93H" で ある。(または頭に "0x" をつけて "0xE93" と書くこともある。これは C言語の表記方法である)


論理演算

さて、以上で2進数(と16進数)自体の説明は終わりなのだが、これだけで は2進数を使うメリットが全然ない。なぜなら、コンピュータでプログラ ムを作ったりシーナリを作ったりするときは10進数を使えばいいからだ。 2進数への変換は全部勝手にやってくれる。シーナリ作成のときはどうし ても16進数を使わなければならないこともあるが、それも電卓を使って 変換すればいいだけのこと。

実は2進数を使う最大のメリットはこの論理演算にあるといってもいい。 ちょっと難しそうな響きがあるが、わかってしまえば簡単である。

論理演算では、1つのビットが真(true)または偽(false)の2つの状態を取 るものと考える。"1" は真を意味し、"0" は偽を表す。

論理演算は、ビットの間の論理を計算するもので、 NOT演算、AND演算, OR演算などがある。NOT 演算は「〜でない」、 AND 演算は「〜でかつ〜」、OR 演算は「〜または〜」という意味になる。

これだけではさっぱりわからないと思うので、順に説明する。

NOT 演算

NOT 演算は「〜でない」という意味、つまり「否定」を表す。 NOT 演算は論理を逆転するだけである。つまり、ビットが 0 なら 1 に、 1 なら 0 にする。
   X       NOT X
-------------------
   0        1
   1        0
例えば、1101B の NOT を計算すると 0010B となる。

AND 演算

AND 演算は「〜でかつ〜」という意味を表す。 AND 演算は、演算を行う2つのビットが両方とも真のときだけ 結果が真となる。
   X      Y      X AND Y
-------------------------
   0      0         0
   0      1         0
   1      0         0
   1      1         1
例えば、1101B と 0110B の AND を計算すると 0100B となる。

OR 演算

OR 演算は「〜または〜」という意味を表す。 OR 演算は、演算を行う2つのビットのいずれかが真のときに結果が真と なる。
   X      Y      X OR Y
-------------------------
   0      0        0
   0      1        1
   1      0        1
   1      1        1

例えば、1100B と 0101B の OR を計算すると 1101B となる。

特定のビットだけを取り出す(マスク)

さて、この論理演算を応用するといろいろ便利なことができる。 特に AND 演算を使って特定のビットだけを取り出すということができる。

例えば、11010101B という数値にの上4ビットだけを取り出すには どうしたらいいだろうか?この場合、11110000B という数値と AND 演算をすればいいのである。


          11010101
     AND  11110000
  -----------------
          11010000
これで上4ビットだけが残り、下の4ビットが 0 になった。 このとき使った 11110000B という数値をマスクパターンという。マスクパターンと AND 演算をすることによって、 特定のビットだけをとりだし、余計なビットを0にして隠す (マスクする)ことができるのである。

シーナリ作成ではこの AND 演算を多用することになる。非常に重要なの でしっかり理解しておいてほしい。


ランニングビットタイマを使ったアニメーション

シーナリ中でアニメーションを行いたい場合、普通は「ランニングビットタイマ」 を使う。ランニングビットタイマは16ビットの変数で、これを使うと およそ 2.7コマ/秒、6秒周期のアニメーションができる。

ランニングビットタイマは、最初は一番右のビットが '1' になっている。 時間がたつと、この '1' が順に左に移動する。一番左に到達すると 一番右に戻り、また左に移動しはじめる。

0000000000000001B   最初はこの状態
0000000000000010B
0000000000000100B
0000000000001000B
0000000000010000B
...
0100000000000000B
1000000000000000B   一番左に達したら、、、
0000000000000001B   一番右に戻る。
0000000000000010B 
...
このビットが一番右から一番左までいって、また一番右に戻るまで 約6秒かかる。よって、ビットが1つ左に移動するのに 6/16 秒、 およそ 0.38 秒かかる。

それぞれのビットの内容を見てオブジェクトを描画してやれば、アニメー ションができるということになる。このときに先程の AND 演算を使うの である。

たとえば、3秒に一回フラッシュするようなライトを作りたいのであれば 0000000100000001B というマスクパターンを用意して、これでマスクをか けてやればいい。すると以下のようになる。

ランニングビットタイマ       マスクパターンと AND 演算したもの
---------------------------------------------------------------
0000000000000001B              0000000000000001B  = 0じゃない!
0000000000000010B              0000000000000000B  = 0
0000000000000100B              0000000000000000B  = 0
0000000000001000B              0000000000000000B  = 0
0000000000010000B              0000000000000000B  = 0
0000000000100000B              0000000000000000B  = 0
0000000001000000B              0000000000000000B  = 0
0000000010000000B              0000000000000000B  = 0
0000000100000000B              0000000100000000B  = 0じゃない!
0000001000000000B              0000000000000000B  = 0
...
0100000000000000B              0000000000000000B  = 0
1000000000000000B              0000000000000000B  = 0
0000000000000001B              0000000000000001B  = 0じゃない!
0000000000000010B              0000000000000000B  = 0
...
つまり、3秒毎に1回ずつ、AND 演算したものが 0 以外の数値に なる。このときにライトを描画し、それ以外のときは描画 しなくすればいい。そうすれば、3秒に一回フラッシュするライトを作れ る。

And 演算を行うには、SCASM の場合は IfVarAnd という命令を使えばいい。 この例の場合は以下のようにする。

       IfVarAnd( :no_light 282 0101 )

       ; ここでライトを描画する

       :no_light

上の IfVarAnd 命令では、変数 282 の内容と 0101H(先程のビットパター ン。16進数で指定していることに注意)の AND をとり、その結果が 0 だった場合には :no_light にジャンプする。変数 282 というのはランニ ングビットタイマのことである。


時間指定でイベントを発生させるには

FS の変数を使うと、時間指定でイベントを発生させることができる。 Scenery SDK を見ると、現在時間は変数 362 と 363 の2つに入っている のでこれを使えそうである。変数362は時間、363 は分になる。

SCASM には IfVarRange という命令があって、これを使うと 変数の値がある範囲にあるかどうかチェックすることができる。 これを使えばいいだろう。

ところがここでちょっとした問題がある。変数 362 と変数 363 は共に 8 ビットの数値なのだが、変数は16ビット単位でしか参照できないように なっているのである。これはどういうことかというと、変数362を見よう とすると、変数 363 まで見えてしまうということである。

例えば、現在時刻を午前 8 時 45 分だとしよう。変数 362 には 8H がは いり、変数 363 には 2DH が入っている。ここで、変数 362 を IfVarRange などを調べようとすると、内容は 2D08H になっているのであ る。つまり、変数 363 の内容が上8ビット、変数 362 の内容が下8ビッ トに入る。そのため、時間指定でイベントを発生させるときにはちょっと したコツが必要になる。

例1: 正午にイベントを起こす

これは簡単。時間が 12(16進数では 0CH)、分が 0 だから、
	IfVarRange( :no_event 362 0x000C 0x000C )

	; イベント

	:no_event
とすればいい。

例2: 毎時30分にイベントを起こす

これもちょっと考えればできる。時間の値はいくつでも良く、 分の値が30(16進数で 1EH)だから、
	IfVarRange( :no_event 362 0x1E00 0x1EFF )
	
	; イベント
	
	:no_event
とする。

例3: 22〜24時までの間イベントを発生させる

これはちょっと難しい。22〜24時までということは、分の値がいくつ であるかに関らずイベントを起こさなければならない。ところが分の値は上8ビッ トだから、IfVarRange が使えないのだ。

時間の値は 0 から 23 まである。 この値が 20, 21, 22, 23 であれば良い。 0 から 23 までの数字を2進数で表すと以下のようになる。

   0       00000000
   1       00000001
   2       00000010
   3       00000011
   4       00000100
   5       00000101
   6       00000110
   7       00000111
   8       00001000
   9       00001001
  10       00001010
  11       00001011
  12       00001100
  13       00001101
  14       00001110
  15       00001111
  16       00010000
  17       00010001
  18       00010010
  19       00010011
  20       00010100
  21       00010101
  22       00010110
  23       00010111
このビットの並びをよーく眺めてみよう。すると、時間の値が 20, 21, 22, 23 であるための条件は 下から5ビット目と3ビット目が共に1である ということがわかる。だから、この判定には IfVarAnd で 行えばいい。
	IfVarAnd( :no_event 362 0x0010 ) ; 下5ビット目の判定
	IfVarAnd( :no_event 362 0x0004 ) ; 下3ビット目の判定

        ; イベント

        :no_event
この機能は名古屋シーナリの花火マクロで使用している。


いかがだったろうか? 2/16進数をいじくるのは最初はちょっと難しいかもしれないが、慣れてしまえば全然たいしたことはないので、いろいろ工夫してみてほしい。


今回の内容についてアンケートにお答えください。
とても面白い まあまあ面白い 普通 あまり面白くない 全然駄目


目次 | 前の話題 | 次の話題