「シフト演算子」

こんにちは、今日のテーマはシフト演算子です。

前回行ったビット演算子と同じように組み込み系のプログラムに使うような気がします。

Unityでもレイヤーの切り替えなんかに使われますよね。

シフト演算子とは

シフト演算子の右にあるオペランド分だけ、シフト演算子の記号に応じて、←や→にビットを移動できます。

これによって、ビットシフトを用いた掛け算などが行えます。



シフト演算子の種類

コンピューター演算では、大きい分けて2つのシフト演算があります。

  • 論理シフト(符号なしデータ)
  • 算術シフト(符号ありデータ)

そして今回私が受けるJavaという言語には、3つのシフト演算子があります。

論理シフト

算術シフトの

とりあえず、外枠から理解したほうが、分かりやすいので論理シフトと算術シフトについて学んでいきましょう。

論理シフト(符号なしデータ)とは、

論理シフトは、算術シフトと異なり対象が正数か負数なのかは考えていません。

とりあえず左にシフトされたら、あふれたビットを捨てて、
空いたビットに0を入れます。

右にシフトした場合も同じです。

ということで、早速ですがJavaの論理シフト演算子の<<と>>>を使っていきます。

左にシフトしたとき(<<)
public class Main {

	public static void main(String[] args) {
		int a=0b1;
		int c=a<<1;
		System.out.println(Integer.toBinaryString(a)+"→"+Integer.toBinaryString(c));
     	System.out.println(c);
     	System.out.println(Integer.toHexString(c));
		}
	}

演算結果
1→10
2
2

上記の結果の様に、左にシフトしたことで、新しく右に0が生まれました。

左シフトしたことによって、aが2倍されましたね。

ちなみに右のオペランドを2→3と変えていくと、演算結果は4,8という風に、
2の右オペランド乗分掛け算した結果になります。

次が難所で、>>>シフトした際です。

右にシフトした時>>>
public class Main {

	public static void main(String[] args) {
		int a=-1;
		int c=a>>>1;
		System.out.println(Integer.toBinaryString(-1)+"→"+Integer.toBinaryString(c));
     	System.out.println(c);
     	System.out.println(Integer.toHexString(c));
		}
	}


11111111111111111111111111111111→1111111111111111111111111111111
2147483647
7fffffff

どうですか、予想外の結果になったと思います。実は、これ演算後結果は間違っていません。aを2進数に変換したものと、cを2進数にしたものを見てみましょう。

一桁ずつ桁を数えてみると、aの方が一桁多いと思います。cの方が、一桁少なく見えると思います。

そこがポイントで、cの一番右の桁には、0が隠れています。

当たり前ですが、分かりにくいです。(こーゆところで、学習の意欲がそがれる。。よね)

右にシフトされたことで、すべての行が右に詰められて、行からあふれた分が、左に0として挿入されています。
一番右の桁(intは32桁目)が0だと負数ではありません。

なぜなら001という負数を正数に直すと、まず負数から1引いて、000値を反転させると111になりますよね。

上記のことと同じことが、今回の演算結果でも見られました。

そのためマイナスを右シフトするときは、算術シフトを使いたいですね。

ここで算術シフトのメリットについてお話します。

算術シフトのメリット

算術シフトを用いれば、ビットを詰めたことで生まれる空ビットに、元のビットの一番端の桁(intだと32桁目)が1か0か確認して、1もしくは0を入れてくれるので、

論理ビットとは違いマイナス演算もできます。

ここで、算術シフトを用いるメリットも分かったと思いますので、プログラムを書いていきましょう。

算術シフトを用いた右シフト
public class Main {

	public static void main(String[] args) {
		int a=-1;
		int c=a>>1;
		System.out.println(Integer.toBinaryString(-1)+"→"+Integer.toBinaryString(c));
     	System.out.println(c);
     	System.out.println(Integer.toHexString(c));
		}
	}

11111111111111111111111111111111→11111111111111111111111111111111
ー1
ffffffff

が、表示された思います。桁数も同じなので、しっかりと空きビットに1を入れてくれたのが、わかりました。

どうでしたか??理解できたかなと思います。

今日のまとめ

  • 論理シフトと算術シフトが存在する
  • 論理シフトは符号を見ない。
  • 算術シフトは符号を見る。
  • <<と>>>が論理シフト
  • >>が算術シフト

感想

<<と>>ってペアみたいだけど、どちらかというと<<と>>>ペアの方がしっくりします。

どちらも論理シフトなので、>>>と>>使う時にどちらが、算術か論理か忘れないようにしたいです。

ちなみに>>が算術です。>>>が論理シフトです。