回答:
左シフトの場合、算術シフトと論理シフトの間に違いはありません。右シフトの場合、シフトのタイプはシフトされる値のタイプによって異なります。
(違いに不慣れな読者のための背景として、1ビットの「論理」右シフトはすべてのビットを右にシフトし、左端のビットを0で埋めます。「算術」シフトは元の値を左端のビットに残します。負の数を扱う場合、違いは重要になります。)
符号なしの値をシフトする場合、Cの>>演算子は論理シフトです。符号付き値をシフトする場合、>>演算子は算術シフトです。
たとえば、32ビットマシンを想定します。
signed int x1 = 5;
assert((x1 >> 1) == 2);
signed int x2 = -5;
assert((x2 >> 1) == -3);
unsigned int x3 = (unsigned int)-5;
assert((x3 >> 1) == 0x7FFFFFFD);
検討i
及びn
シフト演算子の左右のオペランドであること。の型i
、整数昇格後のbe T
。あると仮定n
します[0, sizeof(i) * CHAR_BIT)
—それ以外の場合は未定義—以下のケースがあります。
| Direction | Type | Value (i) | Result |
| ---------- | -------- | --------- | ------------------------ |
| Right (>>) | unsigned | ≥ 0 | −∞ ← (i ÷ 2ⁿ) |
| Right | signed | ≥ 0 | −∞ ← (i ÷ 2ⁿ) |
| Right | signed | < 0 | Implementation-defined† |
| Left (<<) | unsigned | ≥ 0 | (i * 2ⁿ) % (T_MAX + 1) |
| Left | signed | ≥ 0 | (i * 2ⁿ) ‡ |
| Left | signed | < 0 | Undefined |
†ほとんどのコンパイラはこれを算術シフトとして実装し
ますvalue値が結果タイプTをオーバーフローする場合は未定義。私の昇進タイプ
1つ目は、データ型のサイズを気にすることなく、数学的な観点から見た論理シフトと算術シフトの違いです。論理シフトは常に破棄ビットをゼロで埋めますが、算術シフトは左シフトの場合のみゼロで埋めますが、右シフトの場合はMSBをコピーするため、オペランドの符号が保持されます(負の値の2の補数エンコーディングを想定)。
つまり、論理シフトは、シフトされたオペランドを単なるビットのストリームと見なし、結果の値の符号を気にすることなくそれらを移動します。算術シフトはそれを(符号付きの)数値と見なし、シフトが行われるときに符号を保持します。
数値Xのnによる左算術シフトは、Xに2 nを掛けることと同等であり、したがって論理的な左シフトと同等です。MSBはとにかく終わりから外れ、保存するものがないので、論理シフトでも同じ結果が得られます。
数値Xのnによる右算術シフトは、Xが負でない場合にのみ、Xを2 nで整数除算したものと同等です!整数除算は数学的な除算であり、0に向かって丸めます(trunc)。
2の補数エンコーディングで表される負の数の場合、nビットだけ右シフトすると、数学的に2 nで除算され、-∞(floor)に丸められます。したがって、右シフトは非負の値と負の値で異なります。
X≥0の場合、X >> n = X / 2 n = trunc(X÷2 n)
X <0の場合、X >> n = floor(X÷2 n)
ここで÷
数学的な分割は、/
整数除算です。例を見てみましょう:
37)10 = 100101)2
37÷2 = 18.5
37/2 = 18(18.5を0に丸める)= 10010)2 [算術右シフトの結果]
-37)10 = 11011011)2(2の補数、8ビット表現を考慮)
-37÷2 = -18.5
-37 / 2 = -18(18.5を0に丸める)= 11101110)2 [算術右シフトの結果ではない]
-37 >> 1 = -19(-18.5を-∞に丸める)= 11101101)2 [算術右シフトの結果]
ガイ・スティールが指摘し、この矛盾はにつながっている複数のコンパイラのバグ。ここで、非負の(数学)は、符号なしおよび符号付きの非負の値(C)にマッピングできます。どちらも同じように扱われ、整数の除算によって右シフトされます。
したがって、論理値と算術は、左シフトでは等価であり、右シフトでは非負の値と等価です。それらが異なるのは、負の値の右シフトにあります。
標準C99§6.5.7:
各オペランドは整数型でなければなりません。
整数の昇格は、各オペランドで実行されます。結果のタイプは、プロモートされた左オペランドのタイプです。右のオペランドの値が負であるか、プロモートされた左のオペランドの幅以上である場合、動作は未定義です。
short E1 = 1, E2 = 3;
int R = E1 << E2;
上記のスニペットでは、両方のオペランドがint
(整数の昇格により)になります。もしE2
陰性であったりE2 ≥ sizeof(int) * CHAR_BIT
して、操作が定義されていません。これは、使用可能なビットを超えるシフトが確実にオーバーフローするためです。たR
として宣言されshort
、int
シフト演算の結果が暗黙的に変換されますshort
。ナローイング変換。値が宛先タイプで表現できない場合、実装定義の動作につながる可能性があります。
E1 << E2の結果は、E1の左シフトされたE2ビット位置です。空のビットはゼロで埋められます。E1が符号なしの型である場合、結果の値はE1×2 E2であり、結果の型で表現可能な最大値より1を法として減じられます。E1に符号付きの型と負でない値があり、E1×2 E2が結果の型で表現できる場合、それが結果の値になります。それ以外の場合、動作は未定義です。
左シフトは両方とも同じなので、空のビットは単にゼロで埋められます。次に、符号なし型と符号付き型の両方で、算術シフトであると述べています。私はそれを算術シフトと解釈しています。論理シフトはビットで表される値を気にせず、単にビットのストリームと見なすだけだからです。しかし、標準はビットに関してではなく、E1と2 E2の積によって得られる値によってそれを定義することによって話します。
ここでの注意点は、符号付きの型の場合、値は負ではなく、結果の値は結果の型で表現できる必要があることです。それ以外の場合、操作は未定義です。結果の型は、宛先(結果を保持する変数)の型ではなく、整数の昇格を適用した後のE1の型になります。結果の値は、暗黙的に宛先タイプに変換されます。その型で表現できない場合、変換は実装定義です(C99§6.3.1.3/ 3)。
E1が負の値の符号付きタイプの場合、左シフトの動作は未定義です。これは、見落とされがちな未定義の動作への簡単なルートです。
E1 >> E2の結果は、E1の右シフトされたE2ビット位置です。E1が符号なしの型である場合、またはE1が符号付きの型で負でない値である場合、結果の値はE1 / 2 E2の商の整数部分です。E1に符号付きの型と負の値がある場合、結果の値は実装定義です。
符号なしおよび符号付きの非負の値の右シフトは非常に簡単です。空のビットはゼロで埋められます。符号付きの負の値の場合、右シフトの結果は実装定義です。つまり、GCCやVisual C ++などのほとんどの実装では、符号ビットを保持することにより、算術シフトとして右シフトを実装しています。
>>>
通常の>>
andとは別に論理シフトするための特別な演算子を持つJavaとは異なり<<
、CおよびC ++には算術シフトのみがあり、一部の領域は未定義のままであり、実装定義されています。これらを算術と見なす理由は、シフトされたオペランドをビットのストリームとして扱うのではなく、標準的に演算を数学的に表現しているためです。これが、すべてのケースを論理シフトとして定義するだけでなく、実装/定義されていない領域を残す理由です。
-Inf
は、負の数と正の数の両方に向かって丸めます。正の数の0への丸めは、への丸めのプライベートケースです-Inf
。切り捨てるときは、常に正の重み付けの値をドロップするため、それ以外の場合は正確な結果から減算します。
まあ、私はそれをウィキペディアで調べました、そして彼らはこれを言っています:
ただし、Cには1つの右シフト演算子>>しかありません。多くのCコンパイラは、シフトする整数のタイプに応じて、実行する右シフトを選択します。多くの場合、符号付き整数は算術シフトを使用してシフトされ、符号なし整数は論理シフトを使用してシフトされます。
だから、それはあなたのコンパイラに依存するように聞こえます。また、その記事では、左シフトは算術と論理で同じであることに注意してください。ボーダーケース(もちろんハイビットセット)でいくつかの符号付きおよび符号なしの数値を使用して簡単なテストを行い、結果がコンパイラでどのようになるかを確認することをお勧めします。Cには標準がないように思えるので、少なくともそのような依存を回避することが妥当かつ可能である場合、どちらか一方に依存することを回避することもお勧めします。
左方移動 <<
これは何とか簡単で、shift演算子を使用する場合は常にビット単位の演算であるため、doubleおよびfloat演算では使用できません。シフト1のゼロを残したときは常に、常に最下位ビット(LSB
)にます。
しかし、右シフト>>
では、1つの追加ルールに従う必要があり、そのルールは「符号ビットコピー」と呼ばれます。「符号ビットコピー」の意味は、最上位ビット(MSB
)が設定されている場合、再度右シフトした後、MSB
されている場合リセットされた場合に設定され、再度リセットされます。つまり、前の値がゼロだった場合、再度シフトした後、前のビットが1だった場合、ビットはゼロで、シフト後は再び1になります。このルールは左シフトには適用されません。
負の数を右シフトにシフトした場合の右シフトの最も重要な例。次に、シフト後に値が最終的にゼロに到達し、その後、この-1をシフトした場合、値が何回でも同じままになります。チェックしてください。
多くによると c コンパイラ:
<<
算術左シフトまたはビット単位左シフトです。>>
算術右シフトまたはビット単位右シフトです。>>
算術かビット単位(論理)か」と質問されました。あなたは「>>
算術かビットワイズだ」と答えました。それは質問に答えません。
<<
そして>>
演算子は算術ではなく論理的です