シフト演算子
ビットシフト
通常、私たちは使い慣れている 10 進数を使ってプログラムをしていますが、機械語レベルでは全てが 2 進数であり、数値も命令コードもデータも例外ではありません。そこで、算術演算以外にビットを直接扱って演算することもあります。特に、ビットを左右に移動させるシフト演算は時に非常に効果的です。
整数をビット列とみなし、ビット列を左右に動かすにはシフト演算子(Shift operators)を用います。シフト演算には右シフトと左シフトがあります。 右シフトは連続したビットを右に、左シフトはビットを左に平行移動させます。
例えば、上の図のようにビット列を左にシフトすることができます。この図では 8 ビット型(8桁)のデータをシフトしたと仮定しており、最上位のビットがシフト演算で溢れた出ています。シフト演算であふれ出たビットは破棄され、反対側のビットは常に 0 で埋められます。
シフト演算子は、算術演算子同様に 2 項演算子です。左シフト演算子は <<、右シフト演算子は >> 記号で表記します。
式 << シフト数
式 >> シフト数
シフト演算子は左項にシフトする値を指定し、右項にシフトする回数を指定します。双方の項は整数でなければなりません。
class Test { static void Main() { System.Console.WriteLine(2 << 1); System.Console.WriteLine(2 << 2); System.Console.WriteLine(2 << 3); System.Console.WriteLine(2 << 4); } }
コード1は左シフト演算子を用いてビット列を左にシフトしています。ビット列が左に 1 回シフトするということは、純粋に値が倍になるということです。逆に、右に 1 回シフトすると値は半分になります。
算術右シフトと論理右シフト
ここで問題になるのが、シフトすることで発生した空きビットの値です。シフトで溢れた値が抹消されるのは問題ありませんが、その反対側でできた空きビットは一体どの値で初期化されるのでしょう。
通常、反対側にできた空きビットは常に 0 で初期化します。このようなシフト演算を論理シフト(Logical shift)と呼びます。しかし、論理シフトはビット列全体を対象にするため符号に用いられる最上位ビットもシフトしてしまい、負数の場合は符号が反転してしまいます。
右シフトで符号を維持するには、最上位ビットに発生した空きを元の符号ビットで埋めなければなりません。このようなシフト演算を算術シフト(Arithmetic shift)と呼びます。
class Test { static void Main() { System.Console.WriteLine(16 >> 1); System.Console.WriteLine(16 >> 2); System.Console.WriteLine(-16 >> 1); System.Console.WriteLine(-16 >> 2); } }
コード2は負数を右シフトしていますが、実行結果を見れば符号が維持されていることが確認できます。これは、右シフトを行ったときに符号に用いられる最上位ビットを残しているためです。
C#では右シフト演算子の左項が符号を持つ型であれば算術シフトを行い、符号なしであれば論理シフトを行います。もし、負数に対して論理シフトを行いたい場合は型変換を用いて強制的に符号なし型として右シフトします。詳細は後述する型変換を参照してください。