ちりもつもればミルキーウェイ

好奇心に可処分時間が奪われる

算術シフトと論理シフト

はじめに

エンコーディング周りを調べていたら、Goの場合は左辺のオペラントがintかuintかで処理が違うことがわかった。
ドキュメントにも明記されていて、算術シフトと論理シフトというやつを使い分けているらしい。

シフト演算を多用する処理は書いたことがなかったので、これを機に算術シフトと論理シフトの違いをまとめてみた。

論理シフトって?

まずは簡単な方から。論理シフトとは、符号を考慮しないシフトのこと。

符号を考慮しないということは、2の補数表現とかをまったく考慮しなくていいので、素直に与えられたbitを与えられたぶんだけシフトさせればよい。

0b00111111 << 2 は 0b11111100
0b11111100 >> 2 は 0b00111111

かんたん。わかりやすい。たすかる。

算術シフトって?

符号を考慮するシフトのこと。

符号を考慮した値は2の補数表現で表される。算術シフトでは、論理シフトにはない守るべき考慮事項があります

それは、シフト演算は2のn乗の乗算/除算と同じ操作になる必要があるので、負数でもそうなるようにしなければならないということです

そのため、左シフトは常に空いたbitを0でうめて、右シフトでは最上位bitと同じ値で埋める ように操作することでその考慮事項を守ります

0b00010000(32) >> 1 は 0b00010000(16) になる。右シフトなので埋めるbitは符号bitとおなじ0
0b00010000(32) << 1 は 0b01000000(64) になる。左シフトなので埋めるbitは0
0b01000000(64) << 1 は 0b10000000(-128) になる。左シフトなので埋めるbitは0 // オーバーフローすると正しい結果が得られない
0b111000000(-32) >> 1 は 0b11110000(-16) になる。右シフトなので埋めるbitは符号bitとおなじ1
0b111000000(-32) << 1 は 0b11000000(-64) になる。右シフトなので埋めるbitは符号bitとおなじ1
0b10000000(-128) << 1 は 0b00000000(0) になる。左シフトなので埋めるbitは0 // オーバーフローすると正しい結果が得られない

とくに空いたbitの埋め方が論理シフトと毛色がちがうが、これは単に負数の場合でもシフト演算と2のn乗の乗算/除算の操作が等しくなるというルールを守るため
2の補数表現が絡むので複雑になっているだけで、2のn乗の除算/乗算と等しくするためということがわかっていればよいはず

Goだとどう使い分けられてるの?

仕様書に言及がある

golang.org

The shift operators shift the left operand by the shift count specified by the right operand, which must be non-negative. If the shift count is negative at run time, a run-time panic occurs. The shift operators 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. As a result, x << 1 is the same as x*2 and x >> 1 is the same as x/2 but truncated towards negative infinity.

シフト演算については以下のことが言えそう

  • オペランド(移動させるbitの数)は負数だとダメです。実行時panicにさせるぞ!
  • オペランド(シフトさせる値)が符号付き整数だったら算術シフトするぞ!
  • オペランドが符号なし整数だったら論理シフトするぞ!
  • シフトの値に上限はない!
  • 2の倍数の演算とシフト演算はやってることがほとんど同じだけど、シフトした結果切り捨てられたbitが負の無限大方向に切り捨てられるぞ!

つまり、シフトさせたい値がintだったら算術シフトで、uintだったら論理シフトということだ!

感想

  • 論理シフトは簡単
  • 算術シフトはちょっと面食らうけど、2の補数表現を理解していればなぜそうなるかはわかるし筋も通ってるので納得
  • truncated towards negative infinity (負の無限大方向に切り捨てる) という表現をはじめてみた。面白かった
    • ふつうに使う表現っぽい
    • たしかに大きい小さいだけだと絶対値が0にちかいってことですか?と混乱を招きそう
    • そう考えると負の無限大方向(数直線上の常に左に向かって)というのは意味が一意にわかって混乱しない