2の補数の符号は、単に数値をどのように解釈するかによって決まります。3ビットの数字を想像してみてください。
000
001
010
011
100
101
110
111
ゼロと人間にとって自然な数と考えると000、次のように解釈されます。
000: 0
001: 1
010: 2
011: 3
100: 4
101: 5
110: 6
111: 7
これは「符号なし整数」と呼ばれます。あなたはすべてをゼロより大きい/等しい数として見ます。
さて、あなたがいくつかの数を負にしたいとしたらどうしますか?さて、2の補数が助けになります。2の補数は、ほとんどの人に単なる数式として知られていますが、実際には、2 ^ nを法とする合同です。ここで、nは数値のビット数です。
合同の例をいくつか挙げましょう。
2 = 5 = 8 = -1 = -4 module 3
-2 = 6 = 14 module 8
ここで、便宜上、数値の左端のビットをその符号として使用することにしたとします。だからあなたは持っていたい:
000: 0
001: positive
010: positive
011: positive
100: negative
101: negative
110: negative
111: negative
2 ^ 3(= 8)を法として一致する数値を表示すると、次のことがわかります。
4 = -4
5 = -3
6 = -2
7 = -1
したがって、あなたはあなたの番号を次のように見ます:
000: 0
001: 1
010: 2
011: 3
100: -4
101: -3
110: -2
111: -1
ご覧のとおり、-3と5の実際のビット(たとえば)は同じです(数値が3ビットの場合)。x = -3したがって、またはを書くx = 5と同じ結果が得られます。
2 ^ nを法として一致する数値を解釈することには、他の利点があります。マイナスとプラスの2つの数字を合計すると、紙の上でキャリーが捨てられる可能性がありますが、結果は正しいです。なんで?そのキャリーは2^nであり、これは2^nを法として0に一致します。便利じゃないですか?
オーバーフローも合同のもう1つのケースです。この例では、2つの符号なし数値5と6を合計すると、3、つまり実際には11になります。
では、なぜ符号付きと符号なしを使用するのですか?CPUの場合、実際にはほとんど違いはありません。しかしあなたのために:
- 数値がnビットの場合、unsignedは0から2^n-1までの数値を表します
- 数値がnビットの場合、符号付きは-2 ^(n-1)から2 ^(n-1)-1までの数値を表します
したがって、たとえば、符号なしの数値に-1を割り当てる場合、2^n-1を割り当てるのと同じです。
あなたの例によると、それはまさにあなたがしていることです。uint8_tに-3を割り当てていますが、これは不正ですが、CPUに関する限り、253を割り当てています。次に、残りのすべての操作は両方のタイプで同じであり、最終的に同じ結果が得られます。
ただし、例で見逃している点があります。符号付き数値の演算子>>は、シフト時に符号を拡張します。シフトする前の両方の操作の結果は9であるため、これに気付くことはありません。+15がなかった場合は、-6インチiと250インチuになり、>> 2結果は-2(i%u、254で印刷された場合)と62インチになりuます。(いくつかの技術については、以下のPeter Cordesのコメントを参照してください)
これをよりよく理解するために、次の例を見てください。
(signed)101011 (-21) >> 3 ----> 111101 (-3)
(unsigned)101011 ( 43) >> 3 ----> 000101 ( 5)
お気づきのように、floor(-21/8)は実際には-3で、floor(43/8)は5です。ただし、-3と5は等しくありません(64を法として合同ではありません(6ビットがあるため64))。