Other

ビット

整数とビット

ビット位置は最下位のビットを0とします。「ビット0」と言えば最下位のビットです。「ビット1」は最下位から数えて2番目のビットです。

ビット位置 整数値
ビット値 255
指数表現 27 26 25 24 23 22 21 20
整数表現 128 64 32 16 8 4 2 1

例えばビット3とビット5が埋まっていて他のビットがクリアされている値「40」は、次のように考えることができます。

ビット位置 整数値
ビット値 40
指数表現 27 26 25 24 23 22 21 20
整数表現 128 64 32 16 8 4 2 1
0×27 0×26 1×25 0×24 1×23 0×22 0×21 0×20
25 23
32 8 40

ビット0からビット7までを使用する整数は合計8ビットなので、“ビット幅は8”と言ったり、“8ビットの整数”、“この型のサイズは8ビット”または“この型のサイズは1バイト”などと言ったりします。また、“下位4ビット”と言えばビット位置の小さいほう(ビット0)からの4ビットを、“上位3ビット”と言えばビット位置の大きいほうからの3ビット(上の例ではビット5~ビット7)を指します。

符号なし整数の場合、全てのビットが埋まっている値に1を加えるとオーバーフローします。オーバーフローが許される場面では、結果は0です。オーバーフローが許されない場面ではエラーになります。同様に、符号なし整数で全てのビットがクリアされている値から1を引くとアンダーフローが起こります。アンダーフローが許される場面では、結果はその型の取りえる最大値です。アンダーフローが許されない場面ではエラーとなります。

言語によっては、ビット演算時に暗黙の型変換によってint型に昇格することが多いので、一般にビットを扱うときはint型を利用するほうが便利です。そして、int型は32ビットの符号付整数であることが多いです。

値を整数値としてみるとき、その型が符号付整数であるかどうかは、少し重要です。符号付整数の最上位ビットは符号として使われます。このために、ビットを考慮するような場面では、符号付整数の最上位ビットは嫌われる傾向にあります。

ビット位置 整数値
ビット値 -1
指数表現 -1 × 27 26 25 24 23 22 21 20
整数表現 -128 64 32 16 8 4 2 1

実際のプログラムでは「ビット3が1なので、この値は8です」といった考え方はあまりしません。数値として捉えているところでビットのことはあまり考えませんし、ビットを考えている場面では、それが数値としていくつなのかというようなことはどうでも良く、「ビット3が埋まっているかどうか」といったことがわかれば、それで良いのです。

    int n;

	    ...

    if (n & (1 << 3)) {
	    ...
    }

整数を論理値集合として捉える

ビットは0または1の値なので、これを利用して整数を論理値(真偽)の集合として扱うことができます。ビット幅8の整数は8個の論理値を持ちます。

    const int BIT_0 = 1 << 0;
    const int BIT_1 = 1 << 1;
    const int BIT_2 = 1 << 2;
    const int BIT_3 = 1 << 3;
    const int BIT_4 = 1 << 4;
    const int BIT_5 = 1 << 5;
    const int BIT_6 = 1 << 6;
    const int BIT_7 = 1 << 7;

    ...

    int n = 0;

    ...

    if (n & (BIT_0 | BIT_4)) {
	    // BIT_0とBIT_4のどちらかが真

	    ...
    }

特定のビットをクリアするには、そのビットを表す値の補数との論理積を取ります。

    n &= ~BIT_6;

特定のビットを埋めるには、そのビットを表す値との論理和を取ります。

    n |= BIT_6;

特定のビットを反転するには、そのビットを表す値との排他的論理和を取ります。

    n ^= BIT_6;

複数のビットの操作も同様にできます。

    n &= ~(BIT_2 | BIT_6);
    n |= BIT_2 | BIT_6;
    n ^= BIT_2 | BIT_6;

ビット演算はコンピュータが最も得意とするところで、一般的に四則演算よりも高速です。

プログラムでは、論理値をフラグ(flag=旗)と言うことがあります。ビットの説明をするのに、旗が立っている/旗が寝ていると表現されたためではないかと思います。

言語によっては、列挙型で論理値集合をサポートしている場合があります。

補数は全てのビットを反転させたものです。元の値との論理和を取ると全てのビットが埋まっていて、論理積を取ると全てのビットがクリアされています。0は全てのビットがクリアされている状態なので、0の補数は全てのビットが埋まっている状態です。符号付整数であるなら、これは-1です。なぜなら1を加えると全てのビットが繰り上がって0になります。

    int n;

    ...

    printf(" n | ~n = %d\n", n | ~n);
    printf("n & ~n = %d\n", n & ~n );
    printf("~0 = %d\n", ~0);

    --------
    n | ~n = -1
    n & ~n = 0
    ~0 = -1
(2007/11/07 初稿)