★ビット演算(シフト演算)★


次のビット演算は、シフト演算です。

これも2進数で考える演算になります。

シフトというのは、ビットを左右にずらすことを指します。

ビットをずらすとはどのようなイメージでしょうか?


シフト演算の基礎


まずは、左シフトについて説明します。

簡単なので8ビットで考えます。

10進数の1を2進数で表現すると、

  0000 0001

になります。

これを左へ1ビットシフトすると、

  0000 0010

になります。

これをさらに左へ2ビットシフトすると、

  0000 1000

になります。

このようにビットをずらしていくのです。


これだけでは、説明が不十分なので、細かく書いていきます。

     7 6 5 4 3 2 1 0 ← ビット番号
    +--+--+--+--+--+--+--+--+
    |0|0|0|0|0|0|0|1|
    +--+--+--+--+--+--+--+--+

この状態から左へ1ビットシフトすると、

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
  0|0|0|0|0|0|0|1| |
    +--+--+--+--+--+--+--+--+

こうなります。

※あくまでもイメージです

左にはみ出した部分は「オーバーフロー」ですから無くなります。

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |0|0|0|0|0|0|1| |
    +--+--+--+--+--+--+--+--+

右に出来た空欄には、0が入ります。

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |0|0|0|0|0|0|1|0|
    +--+--+--+--+--+--+--+--+

このような仕組みのため、どんどんシフトしてくと0になります。

※これでもまだ説明は不十分ですから、後で補足していきます。


次は右シフトを書きます。

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |0|0|0|0|0|0|1|1|
    +--+--+--+--+--+--+--+--+

この状態から右へ1ビットシフトすると、

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    | |0|0|0|0|0|0|1|1
    +--+--+--+--+--+--+--+--+

こうなります。

右にはみ出した部分は無くなります。

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    | |0|0|0|0|0|0|1|
    +--+--+--+--+--+--+--+--+

左に出来た空欄には、0が入ります。

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |0|0|0|0|0|0|0|1|
    +--+--+--+--+--+--+--+--+

使い道を説明する前に、シフトの細かい種類について説明します。

シフト演算には、

  論理シフト

  算術シフト

の2種類のシフトがあります。

論理シフトはビット操作用、算術シフトは計算用と考えてもらっても構いません。


論理シフト


まずは、簡単な論理シフトから説明します。

論理シフトは単純にビットを左右にずらすだけです。

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |1|1|1|1|1|1|1|1|
    +--+--+--+--+--+--+--+--+

このビット列を右に1ビットシフトすると、

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |0|1|1|1|1|1|1|1|
    +--+--+--+--+--+--+--+--+

こうなります。

このビット列を左に2ビットシフトすると、

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |1|1|1|1|1|1|0|0|
    +--+--+--+--+--+--+--+--+

本当に単純にビットをずらすだけです。


算術シフト


算術シフトはビット列を数値とみなして計算します。

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |0|0|0|0|0|0|0|1|
    +--+--+--+--+--+--+--+--+

これは、10進数で1とみなします。

このビット列を左へ1ビットシフトすると、

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |0|0|0|0|0|0|1|0|
    +--+--+--+--+--+--+--+--+

2になります。

数値として見ると「2倍」になりました。

別のビット列で試してみましょう。

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |0|0|0|0|0|1|1|0|
    +--+--+--+--+--+--+--+--+

これは10進数で6です。

これを左へ1ビットシフトすると、

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |0|0|0|0|1|1|0|0|
    +--+--+--+--+--+--+--+--+

12になりました。

左へ1ビットシフトすることを「2倍する」と考えるのです。

ちなみに、左へ2ビットシフトすると「4倍する」になります。

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |0|0|0|0|0|1|1|0|
    +--+--+--+--+--+--+--+--+

6を左へ2ビットシフトすると、

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |0|0|0|1|1|0|0|0|
    +--+--+--+--+--+--+--+--+

24ですね。


で、この考えを進めていくと

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |0|1|0|0|0|0|0|0|
    +--+--+--+--+--+--+--+--+

これは、10進数で64です。

左へ1ビットシフトすると「2倍」になるはずです。

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |1|0|0|0|0|0|0|0|
    +--+--+--+--+--+--+--+--+

こうなりましたが、ちょっと待ってください。

一番左のビットは「2の補数」で考えるとマイナスを表します。

64を「2倍」すると「−128」になるとおかしいですよね。

そこで算術シフトの場合は、

  最上位ビットは符号を表すビットなのでシフトしない

というルールになっています。

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |0|1|0|0|0|0|0|0|
    +--+--+--+--+--+--+--+--+

この状態で左へ1ビットシフトすると、

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |0|0|0|0|0|0|0|0|
    +--+--+--+--+--+--+--+--+

最上位ビットはシフトの影響を受けず、1は「オーバーフロー」してしまいます。

※ここまで説明しておいて何ですが、後で「どんでん返し」があります・・・


右シフトの場合を書きます。

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |1|0|0|0|0|0|0|0|
    +--+--+--+--+--+--+--+--+

この状態を数値にすると「符号あり」では−128です。

算術シフトでは、これを右にシフトするとちゃんと「2分の1」になります。

最上位ビットはシフトの影響を受けませんので、動きません。

右にシフトした瞬間(?)を書くと、

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |1| |0|0|0|0|0|0|0
    +--+--+--+--+--+--+--+--+

最上位ビット以外がシフトします。

右からはみ出した0は無くなり、第6ビットのところに空欄が出来ます。

この空欄には、「符号ビットのコピー」が入ります。

「符号ビット」は1ですから、↓のようになります。

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |1|1|0|0|0|0|0|0|
    +--+--+--+--+--+--+--+--+

「符号あり」で数値化すると−64になりました。


実際に使ってみる


C言語では、論理シフトと算術シフトの使い分けを以下のように判断しています。

  変数が unsigned の場合、論理シフト

  変数が signed   の場合、算術シフト

また、シフト演算の演算子は、

  左シフト <<

 右シフト >>

となります。

data << 1

と書けば、変数 data の中身を1ビット左シフトする、という意味になります。

それでは、まず論理左シフトから使ってみましょう。

<sample program 180-01>

#include <stdio.h>

int main(void)
{
    unsigned char data = 0x01;

    data = data << 1;

    printf("data = %02X\n", data);

    return 0;
}

<実行結果>

data = 02
続行するには何かキーを押してください・・・

論理シフトを使いたい時には、変数宣言を「符号なし」で行う必要があります。

data = data << 1;

で、dataの中身を左へ1ビットシフトします。

data <<= 1;

でも同じ動きをします。


左シフトを繰り返すプログラムを作ってみます。

<sample program 180-02>

#include <stdio.h>

int main(void)
{
    unsigned char data = 0x01;

    int i;

    for (i = 0; i < 8; i++) {
        data = data << 1;
        printf("data = %02X\n", data);
    }

    return 0;
}

<実行結果>

data = 02
data = 04
data = 08
data = 10
data = 20
data = 40
data = 80
data = 00
続行するには何かキーを押してください・・・

16進数で見ると分かりづらいので、2進数にします。

元データ

  0000 00001 (01)

実行結果

 0000 0010 (02)
  0000 0100 (04)
  0000 1000 (08)
  0001 0000 (10)
  0010 0000 (20)
  0100 0000 (40)
  1000 0000 (80)
  0000 0000 (00)

左シフトを繰り返して行くと右端にあった1が左へシフトされていきます。

最後は、オーバーフローして1が無くなります。


次は論理右シフトです。

<sample program 180-03>

#include <stdio.h>

int main(void)
{
    unsigned char data = 0x80;

    int i;

    for (i = 0; i < 8; i++) {
        data = data >> 1;
        printf("data = %02X\n", data);
    }

    return 0;
}

<実行結果>

data = 40
data = 20
data = 10
data = 08
data = 04
data = 02
data = 01
data = 00
続行するには何かキーを押してください・・・

元データ

  1000 00000 (80)

実行結果

 0100 0000 (40)
 0010 0000 (20)
 0001 0000 (10)
 0000 1000 (08)
 0000 0100 (04)
 0000 0010 (02)
 0000 0001 (01)
 0000 0000 (00)

左端に合った1が右へシフトしていき、最後は0になります。


次は算術シフトを使ってみましょう。

算術右シフトから試してみます。

算術シフトは「符号あり」の変数を使用します。

計算用なので、ビットではなく数値で考えます。

<sample program 180-04>

#include <stdio.h>

int main(void)
{
    char data = -128;

    data >>= 1;

    printf("data = %d\n", data);

    return 0;
}

<実行結果>

data = -64
続行するには何かキーを押してください・・・

説明で書いたように、−128を算術右シフトで1ビットシフトすると「2分の1」の値−64になりました。

繰り返しを使って、連続でシフトしてみましょう。

<sample program 180-05>

#include <stdio.h>

int main(void)
{
    char data = -128;

    int i;

    for (i = 0; i < 8; i++) {
        data >>= 1;
        printf("data = %d\n", data);
    }

    return 0;
}

<実行結果>

data = -64
data = -32
data = -16
data = -8
data = -4
data = -2
data = -1
data = -1
続行するには何かキーを押してください・・・

右に1ビットずつシフトする度に、値が半分になっています。

最後は−1が2回続いていますが、これだけ説明します。

算術シフトは、最上位ビットはシフトされず、コピーされると書きました。

−1という値をビットで表すと↓のようになります。

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |1|1|1|1|1|1|1|1|
    +--+--+--+--+--+--+--+--+

これを右に算術シフトした瞬間(?)の状態は、

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |1| |1|1|1|1|1|1|1
    +--+--+--+--+--+--+--+--+

です。

空欄には、符号ビットがコピーされますから、

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |1|1|1|1|1|1|1|1|
    +--+--+--+--+--+--+--+--+

こうなります。


最後に、算術左シフトを試してみます。

データの初期値を1にして、繰り返し左へシフトしていきます。

<sample program 180-06>

#include <stdio.h>

int main(void)
{
    char data = 1;

    int i;

    for (i = 0; i < 8; i++) {
        data <<= 1;
        printf("data = %d\n", data);
    }

    return 0;
}

<実行結果>

data = 2
data = 4
data = 8
data = 16
data = 32
data = 64
data = -128
data = 0
続行するには何かキーを押してください・・・

これが上で書いた「どんでん返し」です・・・

上の説明では、算術シフトは「符号ビット」をシフトしないと書きました。

しかし、実際に実行してみると64の次は−128になっています。

これをビット列で表すと、

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |0|1|0|0|0|0|0|0|
    +--+--+--+--+--+--+--+--+

これを算術左シフトすると、

     7 6 5 4 3 2 1 0
    +--+--+--+--+--+--+--+--+
    |1|0|0|0|0|0|0|0|
    +--+--+--+--+--+--+--+--+

こうなったと言う事です。

これでは、論理シフトと変わりません・・・

実は、このコンパイラでは算術左シフトは出来ません。

MSDNには、左シフトの説明

  符号付き数値をシフトして符号ビットが影響を受ける場合、結果は未定義です。

と書いてあります。


次回は、論理演算と組み合わせて何か作ってみましょう。


次へ

戻る

目次へ