0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

-3/2と-3>>1は違う

Last updated at Posted at 2025-05-23

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方向と明確になっています。

15.17.2 Division Operator /

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のような意味のないものはないです。

ishl
ishr
iushr

シフトビットの規定

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のため、値は変わらない。

15.19 Shift Operators

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までの範囲とする。

  1. 例えばArrays.binarySearch0の中で、int mid = (low + high) >>> 1;というい記述がありますが、やりたいことはint mid = (low + high) / 2;ですが、>> 1でなく、>>> 1なのは、low + high>0x7FFF_FFFFとなり負の数となっても、論理シフトならば上位(符号)ビットに0が入って、正の数に戻ってくるので、(low + high)/2では負の数のままで問題となるのでしょう。

  2. 負の数aのa>>kなんて書くことはないので、無意識に避けていたのかも。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?