[Go] 算術シフトと論理シフト

シフト演算する際の方法が2通りあることを今更知りました。たぶんどこかで勉強したことはあったはず。その時にちゃんと理解しないままでした。

きっかけは、この記事でGoのシフト演算が他の言語と異なると記載されていたことでした。

それで、動かしてみようと以下のコードを書きました。

	// 最上位が1の場合
	b := 0x81
	fmt.Printf("%d\n", b)
	fmt.Printf("%08b\n", b)
	// 7bitシフト
	fmt.Printf("%08b\n", b>>7)
	// 8bitシフト
	fmt.Printf("%08b\n", b>>8)

動かすと以下の出力になってしまって、全然-1になってないじゃないかとなりました。

129
10000001
00000001
00000000

原因は、b := 0x80 の部分で何となくbyte型で定義した気になっていたけれど、実際はint型になってました。というかちゃんと整数値として129が出力されていますね。。

上の原因に気づくまでに、シフト演算を調べていて、シフト演算には「算術演算」と「論理演算」があることを知りました。言語仕様には以下のように記載されています。

The shift operators shift the left operand by the shift count specified by the right operand. They implement arithmetic shifts if the left operand is a signed integer and logical shifts if it is an unsigned integer. There is no upper limit on the shift count. Shifts behave as if the left operand is shifted n times by 1 for a shift count of n.

後半は上に記載した記事で言及されている、型のサイズを超えてシフトした場合の仕様についてです。前半が、今回知ったシフト演算の方法の話です。

符号付き整数の場合は「算術シフト」、符号なし整数の場合は「論理シフト」されると定義されています。
なるほどと思ったのですが、なぜ異なった方法がとられているのかがわからなかった(全部「論理シフト」すればシンプルなのでは。。。と思った)ので、あと少しだけ調べました。

「算術シフト」は文字通り算術に使用するもので、1ビットの右シフトに除算( /2 )の結果を得られます。符号ビットに応じてシフトにより空いたビットを適切に扱うことで、符号を変化させずに適切な値を算出できます。

試したコードは以下のものです。

	//unsigned
	fmt.Printf("---unsigned---\n")
	var ui8 uint8 = 10
	fmt.Printf("before shifted   = %d (%08b)\n", ui8, byte(ui8))
	fmt.Printf("1bit right shift = %d (%08b)\n", ui8>>1, byte(ui8>>1))
	fmt.Printf("2bit right shift = %d (%08b)\n", ui8>>2, byte(ui8>>2))
	fmt.Printf("1bit left  shift = %d (%08b)\n", ui8<<1, byte(ui8<<1))
	fmt.Printf("2bit left  shift = %d (%08b)\n", ui8<<2, byte(ui8<<2))
	//signed
	fmt.Printf("---signed---\n")
	var i8 int8 = -10
	fmt.Printf("before shifted   = %d (%08b)\n", i8, byte(i8))
	fmt.Printf("1bit right shift = %d (%08b)\n", i8>>1, byte(i8>>1))
	fmt.Printf("2bit right shift = %d (%08b)\n", i8>>2, byte(i8>>2))
	fmt.Printf("1bit left  shift = %d (%08b)\n", i8<<1, byte(i8<<1))
	fmt.Printf("2bit left  shift = %d (%08b)\n", i8<<2, byte(i8<<2))

結果は以下のようになり、符号付き、符号なしともに計算ができています。

---unsigned---
before shifted   = 10 (00001010)
1bit right shift = 5 (00000101)
2bit right shift = 2 (00000010)
1bit left  shift = 20 (00010100)
2bit left  shift = 40 (00101000)
---signed---
before shifted   = -10 (11110110)
1bit right shift = -5 (11111011)
2bit right shift = -3 (11111101)
1bit left  shift = -20 (11101100)
2bit left  shift = -40 (11011000)



この記事が気に入ったらサポートをしてみませんか?