ビット演算
ココでは基数で解説した2進数というものを利用して演算の解説をします。
演算と言っても「四則演算」ではありません。タイトルの「ビット演算」と呼ばれるものです。
演算に入る前に「ビット」とは何なのか、から軽く触れておきます。
ビットとは「デジタルで情報処理する時の最小単位」で0か1の2通りの情報しか記憶が出来ません。
この「ビットが」8個集まったものが「バイト」と呼ばれ、
1バイトで記憶できる情報量は2の8乗(8倍ではない)、256通りです。
文字コードの1バイト文字と呼ばれるものは0〜255の中のどれかになっていますよね?
ちなみに2バイトや全角文字と呼ばれるものは2の16乗、0〜65535まで扱えます。
peekやwpeekがで取り出してコードを表示させてみるとわかると思います。
それではこのビットを使った演算の解説に入りましょう。
先ほど書いたようにビットは0と1の2種類だけです。
通常の計算と若干というか結構似ていると思いますので知らなかった方でもすぐ覚えられることでしょう。
まずはOR演算から。
ORを日本語に訳すと「又は、〜か〜、あるいは」と言った言葉になります。
その通りで演算する数値の片方でも当てはまっていれば真となるわけです。
「真」という言葉を使いましたが、条件が正解・当てはまる時という意味だと思ってください。
逆の間違っている・当てはまらない時は「偽」という語を使い、
コンピュータの世界で偽とは0、真は0以外を指しています。
話を戻します。
何に当てはまるかは演算する数値のどちらかに1つ以上「1」が入っているか、です。
どちらかが当てはまっていれば真ということは、その演算の結果「1」となります。
つまり…わかりやすく表にすると、
| 0 | 0 | 1 | 1 |
| 0 | 1 | 0 | 1 |
| −−−−−−−−−−−−−−−− |
| OR | 0 | 1 | 1 | 1 |
「1 OR 1」を除いて足し算と同じですよね?
その辺は0と1しか使わないということで1OR1も1となると覚えると良いでしょう。
続いてAND演算を説明します。
ANDは「そして、〜と〜」といった意味の通りで、両方とも当てはまる場合にだけ真となります。
どちらか1つでも0が入っていると偽となり演算結果は0になります。
| 0 | 0 | 1 | 1 |
| 0 | 1 | 0 | 1 |
| −−−−−−−−−−−−−−−− |
| AND | 0 | 0 | 0 | 1 |
これは掛け算の結果と同じですね?
さて、ORとAND…HSP内で聞いたことありませんか?
HSPで使う「or(又は|,||)」「and(又は&,&&)」と書いたほうがわかりやすいでしょうか?
そう、stick命令でキーが押されているかを調べるif文の条件の時に使いますよね?
プログラムを始めて間もない頃、私も初めて使う時にヘルプを見てふと疑問に思いましたよ。
「なぜstickの時のif文に使う演算子は『=』じゃなくて『&』なんだろう?」って。
この質問の答えは後に回すとして、ifの条件式に=のほか、&や|が使われます。
これらはどういう時にifの条件に当てはまるのでしょうか?
書き方としては「if X & 2」という感じになるわけですが、「&(AND)の2」は理解できていますか?
先に書いた分だと「ビット演算は0と1しか使わないから、2なんて使えない」となるわけですが、
コチラで説明したように、2は2進数で10、3は11、4は100…となると書いてありましたね?
| 変数X(7) | 0 | 1 | 1 | 1 |
| ANDキー(2) | 0 | 0 | 1 | 0 |
| −−−−−−−−−−−−−−−− |
| 結果(2) | 0 | 0 | 1 | 0 |
つまり、上記のように変数Xに7が入っているなら、「if X & 2」の演算結果は2になります。
ホントにそうなるか気になる方は、下記のようにすることで自分の目で確かめてください。
X = 7
key = 2
mes X & key
stop
先のstick命令だと、カーソル上キー(=2)が押されているかを判断する時に先の条件を使用しますが、
この条件の意味は「押されたキーに上キーが押されているか」を判断するものです。
もし「if X = 2」としていたならば、その条件は「押されたキーは上キーか」を調べるための条件であり、
上キー以外にも別のキーが同時に押されていた場合は偽となります。
たとえ上キー以外が同時に押されていたとしても、上キーが混ざっていれば真とするなら、
&演算子を使うことで正常に押されたことを判定することができます。
同様にif文で複数の条件を指定する時にもORやANDを使います。
「if (x = 1) & (y = 2) | (z = 3)」という条件がどう判定しているのか見てみましょう。
まず、xが1であるか、yが2であるか、zが3であるかをそれぞれ調べて、
「x=1」「y=2」の各結果をAND演算し、ソレの結果と「z=3」のOR演算が一致するかを調べます。
つまり、どんな時が真になるかと言うと、
1.全ての条件に当てはまるとき
2.x = 1 でかつ y = 2であるとき
3.z = 3のとき
上記のうちのいずれかと言うことになります。
なんかわかりにくかった気がしないでもありませんが、コレでANDとORの話を終わります。
続いてXORも説明しておきましょう。
XORはどちらかに1が入っているときだけ真となる、といった言葉では少しわかりにくいものです。
| 0 | 0 | 1 | 1 |
| 0 | 1 | 0 | 1 |
| −−−−−−−−−−−−−−−− |
| XOR | 0 | 1 | 1 | 0 |
上で書いたように「1XOR1」の時は0になります。
引き算のような感じでしょうか?
どんな時にコレが役立つのかと言うと、対象ビットの反転に使えるんです。
反転とは、つまり「0だったら1」「1だったら0」という具合です。
実例を元にもう少し具体的に見てみましょう。
「01010001」を反転させる場合は、反転用の「11111111」を使ってみてください。
| 元の値 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 |
| 反転値 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| −−−−−−−−−−−−−−−−−−−−−− |
| 結果値 | 1 | 0 | 1 | 0 | 1 | 1 | 1 | 0 |
ほら、元の値の正反対になっているでしょう?
当然、反転された値を、更に反転すると元の値に戻ります。
| 元の値 | 1 | 0 | 1 | 0 | 1 | 1 | 1 | 0 |
| 反転値 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| −−−−−−−−−−−−−−−−−−−−−− |
| 結果値 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 |
全部を反転させるのではなく、上位4ビットだけを反転させるには、下位は0を指定すればOK。
| 元の値 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 |
| 反転値 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 |
| −−−−−−−−−−−−−−−−−−−−−− |
| 結果値 | 0 | 1 | 0 | 1 | 1 | 1 | 1 | 0 |
上位は元の「0101」で、下位は反転された「1110」になってますよね?
もとあるデータと特定ビットを反転キーとしてXORすれば、なぞの数値に変わる。
なぞの数値に先と同じ反転キーを使ってXORすれば元の数値に戻る…。
コレはコードを暗号化することに向いていると思いませんか?
ビット反転による暗号化はTIPSに載せてますが、HSPで使用するのにはXORを「^」を使います。
data = 123
key = 9999
angou = data ^ key
fukugou = angou ^ key
mes "暗号化 " + data + " XOR " + key + " = " + angou
mes "復号化 " + angou + " XOR " + key + " = " + fukugou
stop
最後にビットシフトのお話に入ります。
シフトとは「変更」といった意味を指しますが、
ビット(論理)演算において「シフトする」で「ずらす」という意味だと思って問題ないでしょう。
ずらすのは「それぞれのビットにある数値」です。
例.「100 1011(2)」を左に1ビットシフトすると「1001 0110(2)」となる
このように全体をずらし、あいたところには0を入れます。
例では左にずらしましたが、右にずらすと1番右にあった数値は消え、一番左には0が入ります。
このずらした値と元の値を見比べて何か気づきませんか?
例で書いた「100 1011(2)」は1つシフトすると「1001 0110(2)」になる…。
「100 1011(2)」は10進数で75ですよね?
そして「1001 0110(2)」は150(10)で、元の値のぴったり2倍となっています。
右にシフトすると「10 0101(2)」(正確には10 0101.1(2))で、値は37です。
75を2で割ると37.5です。
この場合、小数部は扱わないので0.5は消えてしまうから仕方ないとして2分の1倍した数となります。
これらは偶然でしょうか?
別の数値で試してみましょう。
「1010 0100(2)」を1ビット左シフトすると「1 0100 1000(2)」です。
結果は164から328と、やはり丁度2倍になりました。
右シフトは「101 0010(2)」ですので、164から82で先ほど同様に2分の1倍となりました。
これからお分かりいただけるように1つずらすと2倍、2分の1倍にすることができるわけです。
2つずらすと…どうなるか想像が付いていると思いますけど一応(^^;
「1 1001(2)」を左に2シフトで、「110 0100(2)」。
つまり、25(10)から100(10)、右は「110(2)」で6(10)。
想像していた通り、4倍と4分の1(小数点以下切捨て)となりましたね。
3ビットは6倍…ではありませんよ?
2のX乗倍となりますので、3ビットシフトで8倍と8分の1倍です。
それでは2のX乗倍以外はできないのかというとそうではありません。
例えば先ほどの6倍というものですが、
6倍というのは元の数値を2倍したものと4倍したものを足してやれば6倍になります。
例.「1 0011(2)」を6倍する
1. 2倍(左1シフト)「10 0110(2)」+4倍(左2シフト)「100 1100(2)」
2. 38(10)+76(10)
3. 114(10)は19(2)の6倍
5倍は4倍(左2シフト)したものと元の数値を足してやり、
11倍なら8倍(左3シフト)と4倍(左2シフト)を足して元の数値を引いてやればOK
つまり、何倍、何分の一倍であってもシフトだけで出来るということとなります。
HSPでシフトするには、左シフト「<<」、右シフト「>>」と書きます。
x = 13 ; 元となる値
y = 2 ; 何ビットシフトするか
z = x << y ; 答えを入れておく変数
mes "" + x + "を" + y + "ビット左シフトすると" + z + "となります"
stop
負の整数、即ちマイナス値はどのようにしているのでしょう?
それは「1番左のビットは負数を表す符号にする」と決めているのです。
HSPで使われる32ビット数値だと2進数に直すと桁数が多くなりすぎて見辛いため、
ココでは3ビット数値の範囲で表現するとします。
「1001(2)」が−1(10)なのかというとそうではなく、−1(10)は「1111(2)」です。
同様に−2(10)は「1110(2)」、−3(10)は「 1101(2)」、
−4(10)は「 1100(2)」…−8(10)は「1000 (2)」です。
もう1つ減って「0111 1110(2)」になると、ソレは+127(10)を表します。
つまり、先頭が1の場合は、1減る毎にマイナス値が大きくなると覚えておきましょう。
| 2進数 | 10進数 |
| 1000 | −8 |
| 1001 | −7 |
| 1010 | −6 |
| 1011 | −5 |
| 1100 | −4 |
| 1101 | −3 |
| 1110 | −2 |
| 1111 | −1 |
| 0000 | 0 |
| 0001 | 1 |
| 0010 | 2 |
| 0011 | 3 |
| 0100 | 4 |
| 0101 | 5 |
| 0110 | 6 |
| 0111 | 7 |
さて、−8(10)を単純に1つだけ右シフトすると、「0100(2)」になります。
この数値は+4(10)を表しており、−8(10)を2分の1した−4(10)ではありませんね。
右シフトによって空いた部分に0を入れるシフトのことを論理シフトと呼んでおり、
マイナス符号を持たない2進数の場合には適しているわけですが、
今回のように先頭ビットをマイナス符号として扱う場合には論理シフトは使えません。
先頭ビットは負数を表すルールを使う場合は、
「ビットシフトさせても、先頭ビットはそのままで、先頭ビットをシフトする」ルールも使い、
1ビット右シフトさせる時は先頭ビットを残したまま「1100(2)」という使い方をします。
このシフトを算術シフトと呼んでおり、論理シフトよりも1ビット分だけ最大値が減少しますが、
その分と同じだけのマイナス値を扱うことが可能となります。
HSPを使う上では意識する必要ありませんが、情報処理を勉強する上で必要となることですので、
この先に必要となるという人は、まずはこれらの基本はしっかりと抑えておきましょう。
