シフト演算子
今回はシフト演算子についてみていきます。シフト演算は今までプログラミングしていて実践で使ったことないです。どういう機会で使えるのかはまた調べたいと思います。今回はシフト演算とは何かをみていきます。恐らく科学技術計算などの高速化として使われているのでしょう。ビット演算は加減算より若干速く、乗除算よりずっと高速であるらしいです。
シフト演算とは
シフト演算とは、数値を2進数のビット列とみなして、指定した桁数だけ左または右へずらす演算です。シフト演算には大きく分けて「論理シフト」と「算術シフト」があります。
左シフト
まずは日常的に使っている10進数でみていきます。まずは左シフトです。

上図のサンプルでは7桁までの数字を登録できます。右から1の位、10の位、100の位と日常の10進数の数字です。最上位桁は符号ビットとします。
上図サンプルでは1230という10進数の数字を登録しました。これを左へ2シフトするということは1230の数字の桁が左へ2つ移動 つまり102 = 100倍したことを表しています。シフト演算とは何をしている操作なのかというと簡単にいえば桁をずらしているだけです。
桁をずらすといくつか問題がでてきます。まずは上図サンプルのように左へ2シフトしたら上位ビットがあふれます。あふれたビットの数字はどうなるのか?結果は捨てられます。登録できるビット数が決まっていればそれ以上は登録できません。次は逆です。左へ2シフトしたら右ビットは空になってしまいます。このビットには何を入れるのか?結果は「0」を入れます。
では実際にJavaが行うシフト演算 2進数についてみていきます。考え方は10進数と同じです。

2進数の「1110」を左へ2シフトすると「1110000」になります。(1110)2の10進数は「14」、(1110)2の10進数は「56」なので22 = 4倍になっています。
xビットシフト = 2x倍する
論理右シフト
基本は左シフトと同じです。左にシフトするか右にシフトするかの違いです。

右へ指定された数だけシフトします。右シフトすると下位ビットがあふれてしまいますが捨てられます。上位ビットは空になってしまいますが、そこには「0」を補います。注意しないといけないことは最上位が符号ビットになっている場合、符号ビットを含め右シフトしてしまうということです。符号ビットが0ならば何も問題ないですが1の場合(Javaでは負数)、論理右シフトで予想外の数字が結果として返るかもしれないので注意してください。
上図サンプルではJavaは「2の補数」を使って負数を表していますので(10011100)2の10進数は「-100」です。右シフトだから2の-2乗倍 = 1/4倍の「-25」を期待したいところですが結果は(00100111)2 = 78 になります。Javaで負数の右シフトするときは気をつけましょう。
なお、「2の補数」や「算術演算子の結果のデータ型」については「記事:算術演算子」で説明しましたので参考にしてください。
算術右シフト
論理シフトでは負数を右シフトすると予想外の結果が返ってきます。それは論理シフトは「論理」とあるように右へシフトするという動作をそのまま実行しているからです。算術シフトは「算術」とあるように計算結果に食い違いが起こらないようにシフトします。

右シフトしたときに上位ビット部分が空になります。論理シフトと算術シフトの違いはこの空になったビット部分に何の値を補うかです。論理シフトの場合は「0」固定でした。算術シフトの場合は符号ビットの値を補います。つまりJavaならば正の数なら「0」を負の数ならば「1」が補われます。
上図サンプルではJavaは「2の補数」を使って負数を表していますので(10011100)2の10進数は「-100」です。(11100111)2の10進数は「-25」です。2の-2乗倍 = 1/4倍になり期待通りの結果になりました。
Javaでのシフト演算子
| 左シフト | << | 10 << 2 (10を左へ2シフト) |
|---|---|---|
| 算術右シフト | >> | num>> 2 (変数numを右へ2算術シフト) |
| 論理右シフト | >>> | num>>> 2 (変数numを右へ2論理シフト) |
右シフトには算術と論理がありますが、左シフトはそういう区別はありません。Javaコンパイラは左シフトは論理シフトとみなして動作します。
public class sample01 {
public static void main(String[] args) {
System.out.println( 10 << 2 ); //結果:40
System.out.println( -10 << 2 ); //結果:-40
System.out.println( 10 >> 2 ); //結果:2
System.out.println( -10 >> 2 ); //結果:-3
System.out.println( 10 >>> 2 ); //結果:2
System.out.println( -10 >>> 2 ); //結果:1073741821
}
}
4行目をみてみます。(-10 << 2 ) の結果が「-40」になっています。2ビット分左シフトなので22 = 4倍で期待通りの値です。Javaでは左シフトは論理シフトとして動作すると書きました。右シフトは論理シフトと算術シフトで結果が異なりましたが、左シフトでは論理シフトと算術シフトは結果が同じなのでどちらを使っても問題ないようです。結果が同じなのでJavaでは左シフト演算子は1つしか用意されていません。
最後に負数の左シフトの動きを詳しくみてみます。Javaの整数型リテラルのデータ型デフォルトはint型なので今回は32ビットでみていきます。
10進数の-10は2進数では
(11111111111111111111111111110110)2
です。これを2ビット分の左シフトすると
(11111111111111111111111111011000)2
になります。これは「-40」を表しているので「2の補数」で表す負数をそのまま論理左シフトしても問題がないことがわかりました。

