第19回
いろいろな演算子~ビット演算子

ビット演算の応用例

ビット演算を応用した例を1つ紹介しておきましょう。

属性を調べる処理~int型の値を使った場合

人物(顧客あるいは会員など)の種別を「個人・男、個人・女、組織・営利企業、組織・非営利団体」に分け、どれであるかを表す仕組みを考えます。

単純に考えれば
個人・男:1
個人・女:2
組織・営利:3
組織・非営利:4
――と、それぞれにint型の整数で番号(識別コード)を割り当てればいいでしょう。そして、例えば変数distinctの値がこれらのどれに当てはまるかを調べるなら
#define _MALE 1
#define _FEMALE 2
      :
と記号定数を定義しておき、男性かどうかを調べるなら
if (distinct == _MALE) {
          :
とifで判断するか、
switch (distinct) {
  case _MALE : ...
               break;
とswitch caseで分岐することになるでしょう。

属性を調べる処理~ビット演算を使った場合

ビット演算を用いる場合は、以下のように8ビットの整数で、各ビットごとに性別と組織の種別を表します(Xは未使用ビット)。
桁     8  7  6     5   4   3     2     1
意味   X  X  個人   男   女  組織  営利  非営利
                       (※ 上2桁のXは未使用)
各ビットの状態を取り出すためのビットパターンを、以下のように定義します。
#define _PTN_MALE          48    /* 00110000 個人・男 */

#define _PTN_FEMALE 40 /* 00101000 個人・女 */

#define _PTN_PROFIT 6 /* 00000110 組織・営利 */

#define _PTN_NONPROFIT 5 /* 00000101 組織・非営利 */

#define _PTN_PRIVATE 32 /* 00100000 個人 */

#define _PTN_ORGANIZATION 4 /* 00000100 組織 */
すると、リスト1のような形で、引数の値がどのような意味を持っているかを調べる関数を作れます ※3

リスト1ではビットパターンの演算であることを分かりやすくするために8ビットの符合なし整数(unsigned char)を使っていますが、Cでは最も効率的な型であるint型を使う方が一般的です
リスト1:ビット演算で属性を調べるための関数
/* 各属性を調べるためのビットパターン */
#define _PTN_MALE          48    /* 00110000 個人・男 */
#define _PTN_FEMALE        40    /* 00101000 個人・女 */
#define _PTN_PROFIT         6    /* 00000110 組織・営利 */
#define _PTN_NONPROFIT      5    /* 00000101 組織・非営利 */
#define _PTN_PRIVATE       32    /* 00100000 個人 */
#difine _PTN_ORGANIZATION   4    /* 00000100 組織 */

/* 引数が「個人・男」のときに真(0以外) */
unsigned char isMale(unsigned char n)
{
  return (n & _PTN_MALE);
}

/* 引数が「個人・女」のときに真(0以外) */
unsigned char isFemale(unsigned char n)
{
  return (n & _PTN_FEMALE);
}

/* 引数が「個人」のときに真(0以外) */
unsigned char isPrivate(unsigned char n)
{
  return (n & _PTN_PRIVATE);
}

/* 引数が「組織」のときに真(0以外) */
unsigned char isOrg(unsigned char n)
{
  return (n & _PTN_ORGANIZATION);
}

/* 引数を0にリセット */
void reset_pattern(unsigned char n)
{
  n ^ n;
}

マクロでも可能

先に掲げたビット演算を使わずint型の変数を使う処理の場合、当然のことながらメモリを余分に消費します。また、ifやswitch caseによる条件判断と分岐の構造も、マシンコード(機械語)に展開されたときに冗長なものとなります。

ところが、後者のビット演算を使う方は、マシンコードも短いものになり実行速度も速くなります。また、例に示したように各判定処理やリセット処理を関数として準備しておけば、それを呼び出すコードは案外分かりにくいものではなくなります。

なお、#defineでは定数だけではなく処理をマクロとして定義できるため、関数の代わりに以下のようなマクロを作ることも可能です(マクロ定義に関しては、回を追って紹介します)。
#define  isMale(n)    (n & _PTN_MALE)
#define isPrivate(n) (n & _PTN_PRIVATE)