右シフトには大きく分けて算術右シフトと論理右シフトがあり、見た目は似ていますが結果が変わることがあります。
違いの核心は「左側を何で埋めるか」と「符号(正負)を保つか」です。
本記事ではビットと符号の超入門から入り、正負の具体例、そして言語ごとの演算子の違いまで、8ビット例で直感的に解説します。
右シフトとシフト演算子の基礎
ビットと符号ビットの超入門
コンピュータは数値を0と1の並び(ビット列)で表します。
整数には符号付きの表現があり、そのとき左端のビットを符号ビットと呼びます。
符号ビットが0なら正、1なら負という約束が直感の第一歩です。
通常のプログラミング言語は2の補数表現を使い、負の数は「全ビット反転して1を足す」というルールで作られます。
ここでは細かい理屈に深入りせず、左端が符号を決めると覚えて進みましょう。
右シフトのイメージ
右シフトはビット列を右にずらす操作です。
ずらしたことで右端からはみ出したビットは消え、空いた左側は何かで埋められます。
左側を「符号ビットで埋める」のが算術右シフト、「0で埋める」のが論理右シフトです。
この違いが、負の数を扱うときの結果の差につながります。
右シフトと2で割る関係
正の整数に限れば、右にnビットシフトは概ね2のn乗で割ることと同じ振る舞いになります(小数点以下切り捨て)。
ただし負の数では「割り算」と「右シフト」の丸め方がずれることがあり、常に同じとは限りません。
素直な割り算の意図なら/演算子を、ビット処理の意図ならシフトを、と使い分けるのが安全です。
算術右シフト(>>)とは
符号ビットを保つ
算術右シフトは左側を符号ビットで埋め、数の正負(符号)を保ったまま桁を右に詰めます。
正の数なら左側は0で埋まり、負の数なら1で埋まるので、直感的には「符号を壊さない右シフト」です。
これにより、正の数では2で割るのに近く、負の数でも不自然に大きな正数にはなりません。
正の数の例
8ビットで考えます(実際のintは32ビットが多いです)。
たとえば10進の88は2進で01011000です。
これを2ビット右に算術シフトすると次のようになります。
01011000 (88)
>> 2
00010110 (22)
左側が0で埋まり、88 >> 2 が 22 と、だいたい 88 / 4 に相当する結果になります。
正の数では直感通りに振る舞うため、理解の助けになります。
負の数の例
負の数では左側が1で埋まります。
たとえば-8(8ビット表現では11111000)を2ビット右に算術シフトします。
11111000 (-8)
>> 2
11111110 (-2)
左側が1で埋まることで符号が保たれ、-8 >> 2 は -2 になります。
もう1例として-5を1ビット右に算術シフトすると、2進的には…11111011 >> 1 = …11111101 となり、10進で-3です。
-5 / 2 は -2 なので、割り算と結果がずれることがある点に注意します。
用途: 符号を保った除算
算術右シフトは、「符号を保ったまま2のべき乗で手早く割りたい」場面で使われます。
ただし負の数の丸め方(切り捨て方向)は言語仕様に依存し、/演算子と一致しないことがあります。
可読性の面でも、まずは/を使い、ビットレベルの理由がある場合のみ>>を選ぶと良いでしょう。
論理右シフト(>>>)とは
左側を0で埋める
論理右シフトは左側を常に0で埋める右シフトです。
正の数では算術右シフトと同じ結果になりやすいですが、負の数では大きく異なります。
左側が0で埋まるため、負の数だったものが大きな正の数に見えることがあります。
正の数の例
再び88を使います。
論理右シフトでも結果は同じです。
01011000 (88)
>>> 2
00010110 (22)
正の数では >>> と >> はしばしば同じ結果になります。
このため、負の数で違いが出るまで差に気づきにくいのが実務上の落とし穴です。
負の数の例
-8を2ビット論理右シフトしてみます(8ビット例で直感をつかむための図示です)。
11111000 (-8)
>>> 2
00111110 (62)
左側が0で埋まるので、元は負の数でも大きな正の数(ここでは62)に変わります。
32ビット環境なら桁数が増えるだけで同じ性質が成り立ち、負数が非常に大きな正数に見えることがあるので注意しましょう。
「符号を保つ」目的には論理右シフトを使ってはいけません。
用途: マスク処理とビット操作
論理右シフトは、符号ではなく「ビットの並びそのもの」を扱いたいときに使います。
たとえば上位ビットを抽出したい、ハッシュやCRCの内部計算、ネットワークパケットのフラグ抽出など、0埋めが都合の良い処理と相性がよいです。
負の数を「unsignedとして扱う」ための前処理として使うこともあります。
違いとプログラミング言語の注意点
算術右シフトと論理右シフトの違い
全体像を短く整理します。
「何で左側を埋めるか」「符号を保つか」の2点だけで見分ければ迷いません。
| 項目 | 算術右シフト(>>) | 論理右シフト(>>>) |
|---|---|---|
| 左側の埋め方 | 符号ビット(0または1)で埋める | 0で埋める |
| 符号は保たれるか | 保たれる | 保たれない |
| 正の数での直感 | 2のべき除算に近い | 2のべき除算に近い |
| 負の数の例 | -5 >> 1 = -3 など | -5 >>> 1 は大きな正数に |
| 主な用途 | 符号を保つ右シフト | ビット抽出・マスク処理 |
この表を「負の数で結果が変わる」覚え書きとして手元に置くと安心です。
C言語/C++の注意点
C/C++には>>>演算子はありません。
>>の挙動は型に依存し、符号付き(signed)では処理系依存になりやすく、符号無し(unsigned)では論理右シフトになります。
負の値を論理右シフトしたいなら、まずunsignedにキャストしてから>>を使うのが定石です。
例(C++):
int si = -8; // 符号付き
unsigned ui = (unsigned)si; // 符号なしへ
int ar = si >> 2; // 多くの処理系で算術右シフト
unsigned lr = ui >> 2; // 論理右シフト
「論理右シフトをしたいならunsignedにする」ことをワンセットで覚えると安全です。
ビット幅(32/64)や右シフト量がビット幅以上にならないかにも注意しましょう。
Java/JavaScriptの演算子
Javaには>>と>>>の両方があります。
>>は算術右シフト、>>>は論理右シフトで、int(32ビット)とlong(64ビット)に適用できます。
byteやshortは内部でintに拡張されてから演算されます。
例(Java):
int a = 88;
int b = -8;
System.out.println(a >> 2); // 22
System.out.println(b >> 2); // -2 (算術)
System.out.println(b >>> 2); // 1073741822 など大きな正数
JavaScriptのビット演算子は、内部的に32ビット整数に変換して計算します。
>>>は0埋め右シフトとして振る舞い、負の数を大きな正の数に変えます。
例(JavaScript):
-1 >> 1 // -1 (算術)
-1 >>> 1 // 2147483647 (0埋め、32ビットとして)
「JSの数値は基本64ビット浮動小数ですが、ビット演算は32ビット整数で行われる」点を意識すると混乱が減ります。
初心者のよくあるミスとチェック方法
よくあるのは、「>>は常に/2と同じ」と思い込むミスです。
負の数では結果がずれることがあります。
また、C/C++でsignedの>>を「必ず算術」と決め打ちするのも危険です。
ビット幅の取り違え(8ビットで考えた図を32ビットの実装にそのまま当てはめる)も典型的です。
確認には、いくつかの境界値(0, 1, -1, 最大値, 最小値)で実際に実行し、2進表記を印字するのが効果的です。
たとえば-1の右シフト結果を見れば、算術ならすべて1が並び、論理なら左から0が入るので一目で違いがわかります。
「目で見て確かめる」習慣が最短の理解への近道です。
まとめ
算術右シフト(>>)と論理右シフト(>>>)は、左側を符号ビットで埋めるか(算術)0で埋めるか(論理)という一点の違いが本質です。
正の数では違いが見えにくい一方、負の数では結果が大きく変わります。
C/C++ではunsignedで論理右シフト、Java/JavaScriptでは>>>が論理右シフトという対応を押さえれば、日常のコーディングで迷いにくくなります。
まずは正の数で直感をつかみ、次に-1や-5で挙動を観察することで、実務に必要な理解は十分に身につきます。
「割り算のつもりか」「ビットを動かすつもりか」目的をはっきりさせてから演算子を選ぶことを習慣にしてください。
