第15回 プログラミングについて 『ビットをいじくる話』

『ビットをいじくる話』
ビットをいじくる話。北海道では「いじくる」ことを「ちょす」と言います。まったくの方言です。ちなみに「交換する」は「ばくる」、「平(たいら)」を「ぺったらこい」、「周辺」を「ぐりわ」、「かき混ぜる」を「かまかす」と言います。友人の話ですが、北海道旅行に行く知人に「へっぺ」をおみやげに買ってきてくれと頼んだそうです。その知人は大きなデパートならば売っているだろうと、店員のお姉さんに「へっぺ下さい」と元気良く言ったそうです。お姉さんは顔を真っ赤にして下を向き、「そのような品物た当店では扱っていません」と言ったそうです。「へっぺ」は隠語の方言です。それ以上の説明はここではできません。
話が最初からそれてしまいました。今回はビットを操作する話をしましょう。
通常プログラムを書いていると知らず知らずのうちにビットの操作をしているものです。厳密には加算や減算もビットを操作していることになりますが、ここではこの件はあまり触れないことにします。
通常ビットを操作するときには、整数型の変数を使います。その変数は1バイトでも2バイトでも、4バイトでも構いません。
short data;
data=0;
とすると、整数 data には0が代入されます。これをビット操作の立場からみると、すべてのビットがオフにすることになります。私の場合、変数がビットを操作するためのものであるとき、通常の値として扱うものと区別するために、
data=0x0000;
と16進数で表現しています。コンピュータの本にはスイッチにランプがつながっていて光ったり消えたりする図が描かれていますが、 data のなかにスイッチがあってランプが....などと当てはめると訳がわからなくなってしまいますので、ここではそのことにはこだわらないようにしましょう。
ビットは通常下位から0、1、2、3....と数えます。このビット0をオンにするには次のように行ないます。
(1) data=0x0001;
(2) data|=0x0001;
(3) data|=(0x0001<<0);
(1)は直接値を代入していますが、これは data のすべてのビットがオフのときには問題はないのですが、どれかのビットがオンであるときにはそれらのビットがクリアされてしまいます。(2)では論理演算子ORを使っています。この場合は他のビットには影響を与えません。(3)も(2)と同じですが値の表現の方法がちょっと違っています。どちらを使うかは場合によります。
(3)で使用した << という演算子はシフト演算子といって、ビットを移動(シフト)させるものです。 << は上位方向に移動させ、 >> は下位方向に移動させます。この場合は上位に向って0個シフトさせているので結果は 0x0001 と同じことになります。
それではビット2をオンにするには値をどう表現したらいいのでしょうか。上の例に従って、
(1) data=0x0004;
(2) data|=0x0004;
(3) data|=(0x0001<<2);
となります。ここで注意しなければならないのは、値は10進法ではなく2進法で考えることです。ビットNは2のN乗となります。ビット2ではわかりやすいのですがビット5ともなると人間は頭が悪いのか(私だけかも知れませんが)すぐに分らなくなってしまいます。ちなみにビット5は、
(1) data=0x0020;
(2) data|=0x0020;
(3) data|=(0x0001<<5);
これを10進数で表現すると32となりますが、32ではどうも直感的にはビット5とは理解できません。
これまでは16進数で値を表現しましたが、8進数で表現するのもわかりやすいと思います。ただC言語で数値を表現するときには次のことに注意して下さい。
11 → 10進数で11となる。
011 → 10進数で 9となる。
先頭に0が付いているものは8進数と解釈されるので、数値を見やすくするために10進数の先頭に0を付けるととんでもないことになります。
次に任意のビットの状態を調べるときには次の様にします。
data & 0x0001
これはビット0がオフのときには0、オンのときには1となります。
data & 0x0002
これはビット1がオフのときには0、オンのときには2となります。ですから任意のビットを示す値とANDを行なうことで、0か0以外かで調べることができます。
ビットをシフトさせて調べる方法もあります。ビット3の状態をしらべてみましょう。
unsigned short data;
data=0x000f; /* data の下位の4ビットをオンにする */
data<<=(15-3); /* ビット3が最上位になるようにシフトする */
data>>=15; /* 最上位のビットが最下位になるようにシフトする */
ビット3が最上位になるようにシフトすると、1ビットシフトすつたびに最上位のビットを切捨て、最下位のビットをオフのにしてゆきます。この操作で data 値は 0xf000
となります。いま最上位のビットがビット3ですから、それを最下位になるようにシフトすると、1ビットシフトするたびに最下位のビットが切捨てられ、最上位のビットをオフにしてゆきます。その結果ビット3のみが最下位のビットに残ることになります。
最下位方向にシフトするときに注意しなければならないことが1つあります。上の例では unsigned として符号なしの整数にしていましたが、符号付のときでは最上位のビットに補填されるビットの状態が変わってくるのです。符号付のときはシフトする前の最上位のビットがオンのときにはオンの状態をそうでないときにはオフを補填します。
符号なし:
100000000000000
↓
010000000000000
↓
001000000000000
符号付:
100000000000000
↓
110000000000000
↓
111000000000000
符号なしの型のシフトは論理シフト、符号付の型のシフトは算術シフトと一般に呼ばれています。算術シフトと呼ばれるのは、通常最上位のビットは符号ビットですので、この符号を保ったままシフトすることからその名前がついているのです。
さてこのビットの操作ですが、それでは一体どういうときに使うのかというと、1つはメモリーの節約です。例えば、4種類のオンオフの状態を操作することを考えてみましょう。
char a,c,b,d;
a=0; b=0; c=0; d=0;
:
:
a=1; c=1;
:
:
if(a==1 && d==1){
:
:
}
この通常の変数に値を代入する方法では char 型を使っても4つの変数が必要で、4バイトのメモリーが必要になります。もしこれが配列になると配列の要素数に比例してメモリーを消費することになります。これをビットの操作で表現すると、
#define STS_A 0x01
#define STS_B 0x02
#define STS_C 0x04
#define STS_D 0x08
char status;
status=0x00;
:
:
status|=STS_A;
status|=STS_B;
:
:
if( (status & STS_A)
&&(status & STS_B)){
:
:
}
と表現でき、消費するメモリーは1バイトになります。
また別の用途には計算を単純にして処理スピードを上げる目的にも使います。
例えば、グラフィックエディタなどで線分を表示するとき、表示しなくてもいいのか、クリッピングをするのか、そのまま表示するのかを判断しなければならないときがあります。
| |
A | B | C
| |
----+----+----
| |
D | E | F
| |
----+----+----
| |
G | H | I
| |
いまEの領域が図形を表示する領域としましょう。とすればAは表示領域の左上になり、Fは右側になります。
線分の状態を設定する変数を2バイトとし、1点目の状態を上位バイトに2点目の状態を下位バイトとし、さらに各バイトのビット0~2をYの状態、3~5をXの状態としましょう。さらにその3つのビットを、X方向では左中右の順でビット210の順で、Y方向を上中下をビット210の順とします。
1点目の状態 2点目の状態
上位バイト 下位バイト
76543210 76543210
□□□□□□□□ □□□□□□□□
| X | Y | | X | Y |
いま直線の1点目がAで2点目がCの領域にあると、
1点目の状態 2点目の状態
上位バイト 下位バイト
76543210 76543210
□□□□□□□□ □□□□□□□□
100100 001100
となります。このようにして作成されたビットパターンをどう扱うかはいろいろとあり
ますが、上位バイトと下位バイトのORを行なってみると、
76543210
□□□□□□□□
101100
となります。XまたはY方向が100か001になっているときには表示する必要はありません。010になっているときにはそのまま表示できます。それ以外のときにはクリッピングを行なって表示しなければなりません(クリッピングの結果表示しなくても
いいこともあります)。このクリッピングのビットの立て方を工夫すると、もっと効率よく操作できる可能性がありますので研究してみて下さい。
C言語の構造体にはビットフィールドという便利なものが使用できます。
struct data_strct{
unsigned short a : 1 ;
unsigned short b : 3 ;
unsigned short c : 4 ;
};
このように定義すると、data_strct のメンバー a は1ビットのみの符号なしの文字型となり、0と1のみを扱え、b は3ビットとなり0~7までの値を扱えます。これを使用すると、ビット操作やメモリーの節約になりますので試してみて下さい。扱い方は通常の構造体のメンバーの使用法と同じです。ただ注意することは実際にコンピュータで実行するときにはビットフィールドのメンバーはそれぞれの型に拡張変換され、演算の終了後にフィールドのビット数にされますので、実行速度が遅くなる可能性があることです。
それではまた次回。