2018 年度「計算数学b」 2019-02-08

§11.1 ビットごとの演算子 (&, |, ^, <<, >>, ~)

(11.1.1) 英語では bitwise operators と呼ぶ。 加減乗除よりも速いのが特長である。

(11.1.2) コンピュータの中は「0 と 1 だけの世界」などと言われる。 「0 か 1 か」の情報だけを格納できる…(うまく言えない)…を 1 ビット (bit) という。 いままで整数を格納してきた int 型変数は, いま使っている C コンパイラでは,32 個のビットを並べて用い, 二進法で格納している。 よって,格納できる整数は 0 から 232-1 まで, となりそうだが,およそ半分は負の整数にあてているので, -231 から 231-1 までである。 負の数をどのように格納するかは,ここでは省略する。

(11.1.3) 簡単のため,下の 8 ビットだけを書いて説明する。 ここでは,0 を偽,1 を真とする。 おのおののビットごとに,「かつ」,「または」,「排他的または」 の計算をおこなう演算子がある。

それらをビットごとに計算するので,次のようになる。

かつ
a10110101
b11010011
a & b10010001
    
または
a10110101
b11010011
a | b11110111
    
排他的または
a10110101
b11010011
a ^ b01100110

C 言語での記号は,上の表の左下のますに書いた。 「^」は,マイナスのキーの右どなりにある。 「かつ」は and, 「または」は or, 「排他的または」は xor と呼ぶこともある。 xor は exclusive or の“略”である。

(11.1.4) ほかに,「左シフト」「右シフト」「否定」の演算子がある。 それぞれ,「<<」,「>>」,「~」 と書く。 「~」は,シフトキーを押しながら「^」のキーを打つ。 「a << 2」は,a のビット列を左に 2 だけずらしたものである。 左にはみ出した 2 ビットは消える。 右には,0 が二つ,はいる。 「a >> 2」は,a のビット列を右に 2 だけずらしたものである。 右にはみ出した 2 ビットは消える。 左には,0 が二つ,はいる。 ただし,a が負であった場合については,ここではふれない。 「~a」は,a のビットをすべて反転したものである。 つまり,0 であったビットは 1 に,1 であったビットは 0 になる。

(11.1.5) 十進で入力した整数を二進で出力するプログラム。 「&」と「>>」を使っている。

#include <stdio.h>

int main() {
    int n, mask;

    printf("0 以上,255 以下の整数を入力してください.\n");
    scanf("%d", &n);

    for (mask = 128; mask != 0; mask = mask >> 1) {
        if ((n & mask) != 0) {
            printf("1");
        } else {
            printf("0");
        }
    }
    printf("\n");
}

注意:「if (n & mask != 0)」と書くと別の意味になる。このへんはややこしい。

128 は,二進の 10000000 である。 この数と「&」をとって 0 でないということは, 下から 8 ビットめが 1 であることを意味する。 この 128 を,「>>」を使って右にシフトしてゆく。

(11.1.6) 二進表記の整数を十進で出力するプログラム。 「|」と「<<」を使っている。

#include <stdio.h>

int main() {
    int n, r, mask;

    printf("二進で正の数を入力してください.\n");
    scanf("%d", &n);

    r = 0;
    mask = 1;
    for (    ; n != 0; n = n/10) {
        if (n % 10 != 0) {
            r = r | mask;
        }
        mask = mask << 1;
    }
    printf("%d\n", r);
}

0 と 1 だけが打ち込まれると想定している。 (0 でない数字は 1 とみなされる。)

mask は 1 から始まって,1 ビットずつ左にシフトしてゆく。 rmask の「|」をとったものを r に代入するということは, r のそのビットを 1 に変えることを意味する。

§11.2 パズル --- セイムセット

(11.2.1) 次の 1 から 16 までの組は, 8 種類の果物から重複なく 6 種類を選んだものである。 同じ果物からなるものは何番と何番か? (これは, 朝日新聞の土曜日にはいってくる別刷りの be にときどき載る, セイムセットというパズルを,イラストでなく文字で作ってみたものである。)

 1 スイカ キウイ メロン ミカン レモン バナナ
 2 ミカン スイカ レモン バナナ キウイ イチゴ
 3 ブドウ イチゴ メロン レモン キウイ スイカ
 4 バナナ レモン ミカン ブドウ キウイ イチゴ
 5 スイカ バナナ メロン キウイ ブドウ レモン
 6 ブドウ バナナ レモン スイカ イチゴ キウイ
 7 バナナ ミカン ブドウ スイカ メロン レモン
 8 イチゴ メロン ミカン スイカ レモン バナナ
 9 キウイ バナナ ミカン レモン メロン イチゴ
10 ミカン メロン レモン ブドウ スイカ キウイ
11 メロン イチゴ ミカン ブドウ キウイ バナナ
12 キウイ ミカン レモン イチゴ メロン スイカ
13 メロン バナナ ブドウ キウイ レモン イチゴ
14 イチゴ キウイ レモン ブドウ スイカ バナナ
15 ブドウ スイカ レモン ミカン イチゴ メロン
16 レモン イチゴ バナナ メロン スイカ ブドウ

(11.2.2) たとえば, イチゴを 1, スイカを 2, バナナを 4, レモンを 8, ミカンを 16, キウイを 32, メロンを 64, ブドウを 128 と決めて, 合計を求める方法がある。 それを二進表記で考えれば,順に,00000001, 00000010, 00000100, ..., 10000000 として合計することになる。 くり上がりがないので,楽である。 これをコンピュータでやらせるには, 下から 1 ビットめが 1 ならイチゴが含まれている, 下から 2 ビットめが 1 ならスイカが含まれている, 下から 3 ビットめが 1 ならバナナが含まれている, ……,とすればよい。 間に合わせに書いたプログラムを以下に示す。

(11.2.3) 友だちと分担して,一人 4 行ぐらいずつ入力してみればよいだろう。

(11.2.4) (実は,上のデータをテキストファイルにし, Excel で開き,数値に置換し,合計させてからソートするほうがはるかに楽である。)

#include <stdio.h>

#define N 16    /* 組の数 */

int a[N+1];

int main() {
    int n, i, mask;

    for (   ;   ;   ) {
        printf("何番の組を操作しますか? (0 でプログラムを終了) * ");
        scanf("%d", &n);
        if (n == 0) {
            break;          /* break は,これを囲んでいる最小のループを脱出するもの */
        }
        for (   ;   ;   ) {
            printf("1 イチゴ,2 スイカ,3 バナナ,4 レモン,");
            printf("5 ミカン,6 キウイ,7 メロン,8 ブドウ\n");
            printf("何番を加えますか? 削りますか?\n");
            printf("2 を加えるなら 2,2 を削るなら -2,一覧に戻るには 0 を >> ");
            scanf("%d", &i);
            if (i == 0) {
                break;      /* break は,これを囲んでいる最小のループを脱出するもの */
            }
            if (i > 0 && i <= 8) {
                a[n] = a[n] | (1 << i-1);       /* 加える。| と << を使用 */
            } else if (i < 0 && i >= -8) {
                a[n] = a[n] & ~(1 << -i-1);     /* 削る。& と ~ と << を使用 */
            }
        }
        for (n = 1; n <= N; n++) {              /* 表示。以下でも,<< と & を使用 */
            printf("%2d: ", n);
            for (mask = 1; mask <= 128; mask = mask << 1) {
                if ((a[n] & mask) != 0) {
                    if (mask == 1) {
                        printf("イチゴ ");
                    } else if (mask == 2) {
                        printf("スイカ ");
                    } else if (mask == 4) {
                        printf("バナナ ");
                    } else if (mask == 8) {
                        printf("レモン ");
                    } else if (mask == 16) {
                        printf("ミカン ");
                    } else if (mask == 32) {
                        printf("キウイ ");
                    } else if (mask == 64) {
                        printf("メロン ");
                    } else if (mask == 128) {
                        printf("ブドウ ");
                    }
                }
            }
            printf("合計 %3d\n", a[n]);
        }
    }
}

§11.3 プログラミングが好きになった人への,挑戦的な問題

(11.3.1) 二つの int 型を引数にとり,それらの和を返す関数 int add(int x, int y) を, ビットごとの演算子と代入「=」と比較「!=」だけを使って書け。 くり返しの for も使ってよい。

(11.3.2) ヒントは,あえて書かない。

(11.3.3) 二つの引数が 0 以上の場合に正しく動くように書けばよい。 その関数に負の数を引数として渡すと, ここのパソコンでは正しく計算するようである。


岩瀬順一