v>>kのような右シフトは、よくv/=2^kを高速に計算する小技として説明させる場合があります。実際に、JDKのソースにも、2で割る代わりに1ビット右シフトするようなコードがあります1。
Javaの右シフトには、>>と>>>の使い分けはあるものの、1ビット右シフトすれば、2で割ることと同じと思っていました2。
C言語の仕様と処理系依存
たまたま算術シフトと論理シフトを振り返っていて、C言語に関して勘違いしていたことが分かった。
C言語には、右シフトは>>しかないが、なんとなくむかし(16ビット?)のころは常に0が入ったような気がする。(今となっては確認できない)
でも、singed intとunsinged intと明確に分けているのだから、signed intの場合は算術シフトで、unsigned intの場合は論理シフトとなるのが自然ではないか。(gccやVC++ではそうらしい)
C言語の仕様としては、負の数の右シフトについて規定はないので、処理系依存となる。
x86アセンブラレベルのニーモニックは、sar(算術)とshr(論理)が用意されている。なお、左シフトはsalとshlの両方があるが、どちらも同じ動作。
-3/2について、C99より前は-2となるか-1となるか処理系依存。
C99から-3/2は-1と0方向への切り捨てが仕様となった。
Javaの除算演算子 /の仕様
C99と同様に0方向と明確になっています。
Integer division rounds toward 0.
整数除算は結果を 0 方向に丸める。
-3>>1のビット値
-3を2進数で表現して、右に1つシフトするので、結果が-1でなく-2なのは当然の結果に見えます。
-3 = 0xFFFF_FFFD = 0b1111_1111_1111_1111_1111_1111_1111_1101
-3>>1 = 0xFFFF_FFFE = 0b1111_1111_1111_1111_1111_1111_1111_1110 = -2
-1 = 0xFFFF_FFFF = 0b1111_1111_1111_1111_1111_1111_1111_1111
仮想マシン仕様
せっかくなので、シフト関連のJVM仕様を見たら、ishl、ishr、iushrと3種類用意されている。さすがにiushlのような意味のないものはないです。
シフトビットの規定
INT34-C. 負のビット数のシフトやオペランドのビット数以上のシフトを行わない
実は順番は逆で、ビット全探索のソースを書いていたところ、いつも2秒以内のような制限下では、int型で十分なので、1 << n
のように書いていましたが、nが31以上のときにオーバーフローします。1L << n
としないといけないのですが。
その際に、32ビット左シフトしたら0になると思ったら、1でした。
1 << 30 = 1073741824
1 << 31 = -2147483648
1 << 32 = 1
なんだか20年前に同じことを話していたことを思い出して、確認してみる。
シフト演算子の仕様
int型だとビット幅は5ビット(0x1F)しか有効でないので、32 & 0x1F=0のため、値は変わらない。
If the promoted type of the left-hand operand is int, only the five lowest-order bits of the right-hand operand are used as the shift distance. It is as if the right-hand operand were subjected to a bitwise logical AND operator & (§15.22.1) with the mask value 0x1f. The shift distance actually used is therefore always in the range 0 to 31, inclusive.
もしその左辺オぺランドの昇格された型が int であるならば,その右辺オぺランドの下位5ビットだけをシフト幅として使用する。 それはその右辺オペランドが,マスク値 0x1f を用いたビット単位のAND演算子 & (15.22.1)に従うかのようにとする。 したがって,実際に使用するシフト幅は0から31までの範囲とする。