HOME > 技術談話 > シフト演算復習

はじめに 算術左シフト(マイコン) 算術右シフト(マイコン) 論理左シフト(マイコン) 論理右シフト(マイコン)
CY付き左ローテート(マイコン) CY付き右ローテート(マイコン) 左ローテート(マイコン) 右ローテート(マイコン)
C言語におけるシフト まとめ おまけ


■はじめに

 マイコンを直接操作することのできる組み込み系の制御プログラムでは複数のシフト演算を利用することができます。

 C言語で素直に命令を記述した場合はC言語の制限により利用できる演算の種類は少なくなりますが、コンパイラが提供する機種依存性の強いライブラリを利用すると特殊なシフト演算を利用することができますので、機会があれば是非挑戦してみてください。

 本章では、
・マイコンを直接利用した場合に利用できるシフト演算の動作例
・C言語で「ベタ」に記述した場合のシフト演算の動作例
に分けて解説したいと思います。

■算術左シフト(マイコン)

 ビット列全体を左にシフトし、MSBのビットはCYに移動し、LSBには0が格納されます。


 MSBは最上位ビット、LSBは最下位ビットを示します。CYはキャリーフラグです。

 MSBという表現は非常に都合がよく、値が複数バイトで構成され、最上位ビットのビット位置が何ビット目であろうと、MSBという表現で誰もが納得します。LSBも同様です。

 私はLSBをビット0として扱いますが、人によってはLSBをビット1と表現する場合があります。かつて受験した試験ではLSBがビット1であり、かつ図ではLSBが向かって左、MSBが向かって右に描かれていて戸惑ったことがあります(試験問題作成者の意地悪?)。

 業務を通じて他の会社の技術者と接すると思いますが、仕様書上でLSBがビット0(もしくは1)であることをお互いに確認しておかないと接続試験で動作しない場合があるので注意が必要です。教育環境、国、そして会社によって文化が違うのです。ですが、少なくとも最上位ビット、最下位ビットについてはMSB、LSBという表現であれば万国共通であると思っても差し支えありません。

 さて、このシフト演算は算術演算なので、まずはプラスのバイト型の値で実行してみます。
CY MSB 16進数 10進数 シフト回数
0 0 0 0 0 0 0 0 1 0x01 1 0回目
0 0 0 0 0 0 0 1 0 0x02 2 1回目
0 0 0 0 0 0 1 0 0 0x04 4 2回目
0 0 0 0 0 1 0 0 0 0x08 8 3回目
0 0 0 0 1 0 0 0 0 0x10 16 4回目
0 0 0 1 0 0 0 0 0 0x20 32 5回目
0 0 1 0 0 0 0 0 0 0x40 64 6回目
0 1 0 0 0 0 0 0 0 0x80 -128 7回目
1 0 0 0 0 0 0 0 0 0x00 0 8回目
0 0 0 0 0 0 0 0 0 0x00 0 9回目
 シフトを重ねるたびに値が2倍されます。唯一、7回目のシフトで10進数表現が「-128」になっていますが、符号なし表現であれば「+128」になります。また、総てのビットをシフトすると、以後は「0」となります。

 続いて、マイナスのバイト型の値で実行してみます。
CY MSB 16進数 10進数 シフト回数
0 1 1 1 1 1 1 1 1 0xFF -1 0回目
1 1 1 1 1 1 1 1 0 0xFE -2 1回目
1 1 1 1 1 1 1 0 0 0xFC -4 2回目
1 1 1 1 1 1 0 0 0 0xF8 -8 3回目
1 1 1 1 1 0 0 0 0 0xF0 -16 4回目
1 1 1 1 0 0 0 0 0 0xE0 -32 5回目
1 1 1 0 0 0 0 0 0 0xC0 -64 6回目
1 1 0 0 0 0 0 0 0 0x80 -128 7回目
1 0 0 0 0 0 0 0 0 0x00 0 8回目
0 0 0 0 0 0 0 0 0 0x00 0 9回目
 元の値がマイナスでもシフトを重ねるたびに値が2倍されています。唯一、8回目以降のシフトでは総てのビットをシフトされたために値は「0」となります。

 マイナスを示すMSBはシフトを実行することでマイナス符号としての意味を持たなくなる場合があることに注意が必要です(見かけ上はMSBには1つ右のビットが移動しています)。例えば「-65」を示す10進数(16進数では0xBF)を算術左シフトした場合、「-130」になって欲しいところですが、MSBが消失するために「+126」(16進数では0x7E)になります。
CY MSB 16進数 10進数 シフト回数
0 1 0 1 1 1 1 1 1 0xBF -65 0回目
1 0 1 1 1 1 1 1 0 0x7E 126 1回目

 この現象はバイト型の符号付き整数であるために生じる課題です。
 バイト型の符号付整数における値の有効範囲は「+127〜-128」です。つまり「-130」という値は表現可能な値の有効範囲を超えているのです。この課題については、より大きなデータ型を利用することで回避することができます。

 以上のことから、値がプラス、マイナスに関わらず、符号型、シフト回数、そして、データ型に応じた値の有効範囲に注意すれば算術演算での2倍(4倍、8倍、…)をシフト演算で代用することができます。 

■算術右シフト(マイコン)

 ビット全体を右にシフトし、LSBのビットはCYに移動し、MSBの値は変化しません。



 MSBの値が変化しないところが算術右シフトならではの特徴です。

 では、算術右シフトについてもプラスのバイト型の値で実行してみます。
CY MSB 16進数 10進数 シフト回数
0 0 1 0 0 0 0 0 0 0x40 64 0回目
0 0 0 1 0 0 0 0 0 0x20 32 1回目
0 0 0 0 1 0 0 0 0 0x10 16 2回目
0 0 0 0 0 1 0 0 0 0x08 8 3回目
0 0 0 0 0 0 1 0 0 0x04 4 4回目
0 0 0 0 0 0 0 1 0 0x02 2 5回目
0 0 0 0 0 0 0 0 1 0x01 1 6回目
1 0 0 0 0 0 0 0 0 0x00 0 7回目
0 0 0 0 0 0 0 0 0 0x00 0 8回目
 シフトを重ねるたびに値が2分の1されます。そして、総てのビットをシフトすると、以後は「0」となります。
 予断ですが、CYに移動したビット(0もしくは1)をバイトデータに加算すると、2で割った際に生じた小数点以下第1位を四捨五入した事になります(詳しくは2進数の解説へ)。

 続いて、マイナスのバイト型の値で実行してみます。
CY MSB 16進数 10進数 シフト回数
0 1 0 0 0 0 0 0 0 0x80 -128 0回目
0 1 1 0 0 0 0 0 0 0xC0 -64 1回目
0 1 1 1 0 0 0 0 0 0xE0 -32 2回目
0 1 1 1 1 0 0 0 0 0xF0 -16 3回目
0 1 1 1 1 1 0 0 0 0xF8 -8 4回目
0 1 1 1 1 1 1 0 0 0xFC -4 5回目
0 1 1 1 1 1 1 1 0 0xFE -2 6回目
0 1 1 1 1 1 1 1 1 0xFF -1 7回目
1 1 1 1 1 1 1 1 1 0xFF -1 8回目
1 1 1 1 1 1 1 1 1 0xFF -1 9回目
 シフトを重ねるたびに値が2分の1されます。総てのビットをシフトすると以後は「-1」になります。

 以上のことから、値がプラス、マイナスに関わらず、符号型、シフト回数に注意すれば算術演算での2分の1(4分の1、8分の1、…)をシフト演算で代用することができます。 

■論理左シフト(マイコン)

 ビット列全体を左にシフトし、MSBのビットはCYに移動し、LSBには0が格納されます。


 動作内容は前述の「算術左シフト」と同じですが。
 算術シフトと異なり、主に符号なし整数の左シフトに用います。例えばビット位置を移動したりする場合に用います。

 では、バイト型の値で実行してみます。
CY MSB 16進数 10進数 シフト回数
0 0 0 0 0 0 0 0 1 0x01 1 0回目
0 0 0 0 0 0 0 1 0 0x02 2 1回目
0 0 0 0 0 0 1 0 0 0x04 4 2回目
0 0 0 0 0 1 0 0 0 0x08 8 3回目
0 0 0 0 1 0 0 0 0 0x10 16 4回目
0 0 0 1 0 0 0 0 0 0x20 32 5回目
0 0 1 0 0 0 0 0 0 0x40 64 6回目
0 1 0 0 0 0 0 0 0 0x80 128 7回目
1 0 0 0 0 0 0 0 0 0x00 0 8回目
0 0 0 0 0 0 0 0 0 0x00 0 9回目
 シフトを重ねるたびに結果的に値が2倍されます。そして、総てのビットをシフトすると、以後は「0」となります。
 算術シフト、論理シフトに共通しますが、シフトにより消失するビットがあることに注意してください。

■論理右シフト(マイコン)

 ビット全体を右にシフトし、LSBのビットはCYに移動し、MSBには0が格納されます。


 算術右シフトと異なりMSBには常に0が格納されます。
 算術シフトと異なり、主に符号なし整数の右シフトに用います。例えばビット位置を移動したりする場合に用います。

 では、バイト型の値で実行してみます。
CY MSB 16進数 10進数 シフト回数
0 1 0 0 0 0 0 0 0 0x80 128 0回目
0 0 1 0 0 0 0 0 0 0x40 64 1回目
0 0 0 1 0 0 0 0 0 0x20 32 2回目
0 0 0 0 1 0 0 0 0 0x10 16 3回目
0 0 0 0 0 1 0 0 0 0x08 8 4回目
0 0 0 0 0 0 1 0 0 0x04 4 5回目
0 0 0 0 0 0 0 1 0 0x02 2 6回目
0 0 0 0 0 0 0 0 1 0x01 1 7回目
1 0 0 0 0 0 0 0 0 0x00 0 8回目
0 0 0 0 0 0 0 0 0 0x00 0 9回目
 シフトを重ねるたびに結果的に値が2分の1されます。そして、総てのビットをシフトすると、以後は「0」となります。
 算術シフト、論理シフトに共通しますが、シフトにより消失するビットがあることに注意してください。

■CY付き左ローテート(マイコン)

 ビット列全体を左にシフトし、CYのビットはLSBに、そしてMSBのビットはCYに移動します。


 主に符号なし整数の左シフトで用いられ、ビット位置を移動する場合に用います。
 これまでのシフトと異なり、CYにビットが移動している場合はありますが、ビットが完全消失することはありません。

 では、バイト型の値で実行してみます。
CY MSB 16進数 10進数 シフト回数
0 0 0 0 0 0 1 0 1 0x05 5 0回目
0 0 0 0 0 1 0 1 0 0x0A 10 1回目
0 0 0 0 1 0 1 0 0 0x14 20 2回目
0 0 0 1 0 1 0 0 0 0x28 40 3回目
0 0 1 0 1 0 0 0 0 0x50 80 4回目
0 1 0 1 0 0 0 0 0 0xA0 160 5回目
1 0 1 0 0 0 0 0 0 0x40 64 6回目
0 1 0 0 0 0 0 0 1 0x81 129 7回目
1 0 0 0 0 0 0 1 0 0x02 2 8回目
0 0 0 0 0 0 1 0 1 0x05 5 9回目
 ビットが消失しないので算術演算用途よりも論理演算や移動用途として利用することが多いです。

■CY付き右ローテート(マイコン)

 ビット列全体を右にシフトし、CYのビットはMSBに、そしてLSBのビットはCYに移動します。


 主に符号なし整数の右シフトで用いられ、ビット位置を移動する場合に用います。
 これまでのシフトと異なり、CYにビットが移動している場合はありますが、ビットが完全消失することはありません。

 では、バイト型の値で実行してみます。
CY MSB 16進数 10進数 シフト回数
0 0 0 0 0 0 1 0 1 0x05 5 0回目
1 0 0 0 0 0 0 1 0 0x02 2 1回目
0 1 0 0 0 0 0 0 1 0x81 129 2回目
1 0 1 0 0 0 0 0 0 0x40 64 3回目
0 1 0 1 0 0 0 0 0 0xA0 160 4回目
0 0 1 0 1 0 0 0 0 0x50 80 5回目
0 0 0 1 0 1 0 0 0 0x28 40 6回目
0 0 0 0 1 0 1 0 0 0x14 20 7回目
0 0 0 0 0 1 0 1 0 0x0A 10 8回目
0 0 0 0 0 0 1 0 1 0x05 5 9回目
 ビットが消失しないので算術演算用途よりも論理演算や移動用途として利用することが多いです。

■左ローテート(マイコン)

 ビット列全体を左にシフトし、MSBのビットはCYとLSBに移動します。


 主に符号なし整数の左シフトで用いられ、ビット位置を移動する場合に用います。
 これまでのシフトと異なり、ビットが完全消失することはありません。

 では、バイト型の値で実行してみます。
CY MSB 16進数 10進数 シフト回数
0 0 0 0 0 0 1 0 1 0x05 5 0回目
0 0 0 0 0 1 0 1 0 0x0A 10 1回目
0 0 0 0 1 0 1 0 0 0x14 20 2回目
0 0 0 1 0 1 0 0 0 0x28 40 3回目
0 0 1 0 1 0 0 0 0 0x50 80 4回目
0 1 0 1 0 0 0 0 0 0xA0 160 5回目
1 0 1 0 0 0 0 0 1 0x41 65 6回目
0 1 0 0 0 0 0 1 0 0x82 130 7回目
1 0 0 0 0 0 1 0 1 0x05 5 8回目
0 0 0 0 0 1 0 1 0 0x0A 10 9回目
 ビットが消失しないので算術演算用途よりも論理演算や移動用途として利用することが多いです。

■右ローテート(マイコン)

 ビット列全体を右にシフトし、LSBのビットはCYとMSBに移動します。

 主に符号なし整数の右シフトで用いられ、ビット位置を移動する場合に用います。
 これまでのシフトと異なり、ビットが完全消失することはありません。

 では、バイト型の値で実行してみます。
CY MSB 16進数 10進数 シフト回数
0 0 0 0 0 0 1 0 1 0x05 5 0回目
1 1 0 0 0 0 0 1 0 0x82 130 1回目
0 0 1 0 0 0 0 0 1 0x41 65 2回目
1 1 0 1 0 0 0 0 0 0xA0 160 3回目
0 0 1 0 1 0 0 0 0 0x50 80 4回目
0 0 0 1 0 1 0 0 0 0x28 40 5回目
0 0 0 0 1 0 1 0 0 0x14 20 6回目
0 0 0 0 0 1 0 1 0 0x0A 10 7回目
0 0 0 0 0 0 1 0 1 0x05 5 8回目
1 1 0 0 0 0 0 1 0 0x82 130 9回目
 ビットが消失しないので算術演算用途よりも論理演算や移動用途として利用することが多いです。

■C言語におけるシフト

 C言語でシフト演算を利用する時は
・左シフト … << (代入演算のときは「<<=」を利用できます)
・右シフト … >> (代入演算のときは「>>=」を利用できます)
を使います。
 基本的にはシフト動作のみでローテーション動作は行われません。また、CYビットを直視することはできません。

 それでは実際にプログラムを作成してシフト動作を確認してみましょう。
 実験用に作成したサンプルプログラムがありますので、これをダウンロードして追試することもできます。

 さて結果ですが…(実行記録


 2進数のビットで見た場合、左シフトは符号の影響を受けませんが、右シフトの場合は符号の影響を受けています。私自身、シフトを行う変数は符号なしで定義していますが、今回の結果は結果は予測できるにせよ、符号ありで定義されている人は要注意です。特にビットを移動しようとしている場合は混乱を招きます。
 この特性を理解した上で使用する分には問題ありません。意図的に解析が困難なトリッキーな処理ができるかもしれませんが、見易さと言う面では符号なし変数を利用したほうが無難です。

(1)左シフト

(1−1)符号付き変数
最初の値はプラス 最初の値はマイナス
回数 16進数 10進数 2進数 回数 16進数 10進数 2進数
0回目 0x01 1 00000001 0回目 0xFF -1 11111111
1回目 0x02 2 00000010 1回目 0xFE -2 11111110
2回目 0x04 4 00000100 2回目 0xFC -4 11111100
3回目 0x08 8 00001000 3回目 0xF8 -8 11111000
4回目 0x10 16 00010000 4回目 0xF0 -16 11110000
5回目 0x20 32 00100000 5回目 0xE0 -32 11100000
6回目 0x40 64 01000000 6回目 0xC0 -64 11000000
7回目 0x80 -128 10000000 7回目 0x80 -128 10000000
8回目 0x00 0 00000000 8回目 0x00 0 00000000
9回目 0x00 0 00000000 9回目 0x00 0 00000000
(1−2)符号なし変数
最初の値はプラス相当 最初の値はマイナス相当
回数 16進数 10進数 2進数 回数 16進数 10進数 2進数
0回目 0x01 1 00000001 0回目 0xFF 255 11111111
1回目 0x02 2 00000010 1回目 0xFE 254 11111110
2回目 0x04 4 00000100 2回目 0xFC 252 11111100
3回目 0x08 8 00001000 3回目 0xF8 248 11111000
4回目 0x10 16 00010000 4回目 0xF0 240 11110000
5回目 0x20 32 00100000 5回目 0xE0 224 11100000
6回目 0x40 64 01000000 6回目 0xC0 192 11000000
7回目 0x80 128 10000000 7回目 0x80 128 10000000
8回目 0x00 0 00000000 8回目 0x00 0 00000000
9回目 0x00 0 00000000 9回目 0x00 0 00000000

(2)右シフト

(2−1)符号付き変数
最初の値はプラス 最初の値はマイナス
回数 16進数 10進数 2進数 回数 16進数 10進数 2進数
0回目 0x40 64 01000000 0回目 0x80 -128 10000000
1回目 0x20 32 00100000 1回目 0xc0 -64 11000000
2回目 0x10 16 00010000 2回目 0xe0 -32 11100000
3回目 0x08 8 00001000 3回目 0xf0 -16 11110000
4回目 0x04 4 00000100 4回目 0xf8 -8 11111000
5回目 0x02 2 00000010 5回目 0xfc -4 11111100
6回目 0x01 1 00000001 6回目 0xfe -2 11111110
7回目 0x00 0 00000000 7回目 0xff -1 11111111
8回目 0x00 0 00000000 8回目 0xff -1 11111111
9回目 0x00 0 00000000 9回目 0xff -1 11111111
(2−2)符号なし変数
最初の値はプラス相当 最初の値はマイナス相当
回数 16進数 10進数 2進数 回数 16進数 10進数 2進数
0回目 0x40 64 01000000 0回目 0x80 128 10000000
1回目 0x20 32 00100000 1回目 0x40 64 01000000
2回目 0x10 16 00010000 2回目 0x20 32 00100000
3回目 0x08 8 00001000 3回目 0x10 16 00010000
4回目 0x04 4 00000100 4回目 0x08 8 00001000
5回目 0x02 2 00000010 5回目 0x04 4 00000100
6回目 0x01 1 00000001 6回目 0x02 2 00000010
7回目 0x00 0 00000000 7回目 0x01 1 00000001
8回目 0x00 0 00000000 8回目 0x00 0 00000000
9回目 0x00 0 00000000 9回目 0x00 0 00000000

■まとめ

 シフト演算を利用する時、その実現方法に関わらず。
符号の有無
シフト回数によるオーバーフローの可能性
データ型に応じた有効範囲を意識したシフト
について
十二分に机上検証してから使うことが大切です。

■おまけ

 シフトの実行により値を2倍、4倍…。2分の1、4分の1…を容易に、かつ高速に行うことができます。

 ですが、ここで注意点。
 シフトを使用した割り算には余りが発生せず、常に小数点以下切捨ての値になってしまいます。

 例えば、5を2で割る。つまり、右に1回シフトすると2を得られますが、何でもかんでも必要回数分右にシフトで済ませてしまうと必ず理想値に対してアンダーになってしまいます。時にはアンダーのままで良いのですが、値を平均化する場合には支障があります。

 そこで、これを回避するために小数点以下第1位を四捨五入することが求められますが、シフトによる余りが生成されないので少し「コツ」が必要になります。

 マイコンを直接利用することができ、かつ、アセンブラで命令を記述できる場合は、必要回数分シフトした後でCYを含めて0を加算、もしくはCY=1の場合に+1することで小数点以下第1位を四捨五入した事になります。

 一方、C言語だけで記述する場合には少しだけ命令を加えます。

 必要なシフト回数を1回だけ少なく右シフトした後で、LSBの1ビットを論理演算(AND)して抽出します。その後、更に右1回シフトし、先に抽出したLSBを加算します。

 あえて図は掲載しませんので、各自で机上検証してみましょう。

Copyright 2007-2009 PaletteSoft LLC. All rights reserved. 利用条件 | HOME | サイトマップ | お問合せ