コンピュータが負の数を扱う標準的な方法が「2の補数」です。
2の補数を理解すると、負の数の保存、足し算や引き算、桁あふれの判定まで一気に分かりやすくなります。
この記事では、基礎から作り方・読み方、計算のコツ、よくあるつまずきまで、初心者向けに順を追ってやさしく解説します。
2の補数とは?補数表現の基本
なぜ使うか
2の補数は、負の数を2進数で表す方法の定番です。
足し算の回路だけで引き算も実現できるので、ハードウェアが単純になり高速に動作します。
また、0の表現が1つだけであること、大小比較が自然にできること、オーバーフローの検出が簡単であることも採用理由です。
結果として、ほとんどのプログラミング言語やCPUが2の補数を使っています。
2進数とビットの基礎
2進数は0と1だけで数を表します。
右端の桁から1、2、4、8…と2のべき乗の重みを持ち、1の桁分だけ合計します。
例えば「0101」は4と1の和で5です。
ビット幅がnなら、0から2^n−1までのパターンが存在します。
2の補数でもこの「ビットの重み」は基本に同じです。
符号ビットとビット幅
2の補数では最上位ビット(MSB)が符号の役割を担い、0なら非負、1なら負を示します。
ただし「符号ビットが別にある」のではなく、MSB自体が重み−2^(n−1)として数値に参加します。
これにより、1つの加算器で正負を区別なく計算できます。
ビット幅は必ず決めて扱い、8ビット、16ビットなど固定幅で考えます。
符号なし整数との違い
同じビット列でも解釈が違います。
MSBが1のとき、符号なしでは大きな正の数、2の補数では負の数になります。
例えば8ビットの「11111111」は符号なしで255、2の補数では−1です。
プログラムでは型や表示方法により解釈が変わるため注意が必要です。
表の前後に空行を入れます。
| 8ビットのビット列 | 符号なしの値 | 2の補数の値 |
|---|---|---|
| 01111111 | 127 | 127 |
| 10000000 | 128 | −128 |
| 11111111 | 255 | −1 |
2の補数の作り方・読み方
手順
負の数の2の補数は、次の2通りのどちらでも作れます。
やりやすい方を選んで同じビット幅で統一することが大切です。
- 反転して1を足す手順
- 絶対値を2進数にする
- ビットを0↔1で反転する
- 最下位ビットから1を加える
- 引き算で求める手順
- 2^n − |x| を計算する(結果をnビットで表す)
- 例えば8ビットで−13なら256−13=243→11110011
正の数はそのまま2進数にして、左側を0で埋めます。
必ず事前にビット幅を決めてから作業します。
8ビット幅での例です。
- +13: 00001101。正の数は普通に2進数化して0で左を埋めるだけです。
- −13: 13は00001101 → 反転11110010 → 1を足すと11110011。よって11110011。
- −1: 全ビットが1で11111111。反転(00000000)に1を足した形とも言えます。
- −128: 2^8−128=128で10000000。最小値はMSBのみ1で他は0のパターンになります。
10進数⇔2の補数の変換
- 10進→2の補数
- 非負の値は2進数化して左を0で埋める
- 負の値は2^n−|x|を計算するか、反転+1で求める
- 範囲に収まることを確認します
- 2の補数→10進
- MSBが0なら符号なしのまま読む
- MSBが1なら、2^nを引くか、反転+1して得た絶対値に負符号を付ける
直感をつかむために4ビットの対応表を示します。
表の前後に空行を入れます。
| 4ビット | 10進値 | 4ビット | 10進値 |
|---|---|---|---|
| 0000 | 0 | 1000 | −8 |
| 0001 | 1 | 1001 | −7 |
| 0010 | 2 | 1010 | −6 |
| 0011 | 3 | 1011 | −5 |
| 0100 | 4 | 1100 | −4 |
| 0101 | 5 | 1101 | −3 |
| 0110 | 6 | 1110 | −2 |
| 0111 | 7 | 1111 | −1 |
MSBが1の領域が負の値を表すことが視覚的に分かります。
符号拡張
ビット幅を広げるときは、左側にMSBを複製して埋めます。
これを符号拡張といいます。
例えば8ビットの−5(11111011)を16ビットにするなら11111111 11111011です。
一方、正の5(00000101)は00000000 00000101になります。
0埋めは符号なしのゼロ拡張で、負の値を扱うときは不適切です。
表現できる範囲
2の補数のnビットで表せる範囲は −2^(n−1) から 2^(n−1)−1 です。
表の前後に空行を入れます。
| ビット幅 | 最小値 | 最大値 |
|---|---|---|
| 4 | −8 | 7 |
| 8 | −128 | 127 |
| 16 | −32768 | 32767 |
| 32 | −2147483648 | 2147483647 |
この範囲外はオーバーフローになります。
0が1つだけの理由
1の補数や符号付き絶対値表現では、+0と−0の2種類がありました。
2の補数では反転+1の規則により、−0を作ると再び0に戻るため、0は1通りだけになります。
これにより比較や計算が簡潔になります。
2の補数での計算
足し算はそのまま加算
2の補数では足し算は通常の2進加算と同じで、そのまま加えます。
桁あふれの最終キャリーは基本的に捨てます。
符号の違いにかかわらず、1つの加算器で完結します。
引き算は相手を2の補数にして加算
a−bは、bを2の補数で負にして a+(−b) として計算します。
引き算でも実際にやるのは加算だけです。
これがハードウェアが単純化できる理由です。
計算例(7+(-3)、5-9)
8ビットの例で確認します。
- 7+(−3)
- 7は00000111、−3は11111101
- 加えると 00000111+11111101=1 00000100 (先頭の1はキャリー)
- キャリーを捨てて 00000100。結果は4で正しいです。
- 5−9 は 5+(−9)
- 9は00001001、−9は11110111
- 00000101+11110111=11111100
- 11111100はMSBが1なので負。反転00000011に1を足すと00000100で4。よって−4です。
どちらも最後のキャリーは結果に含めません。
オーバーフローの見分け方
初心者におすすめの判定は次の通りです。
同符号同士の加算で結果の符号が変わったらオーバーフローです。
- 例: 8ビットで 127(01111111)+1(00000001)=10000000 は負になり、範囲外なのでオーバーフロー。
- 例: 8ビットで −128(10000000)+−1(11111111)=01111111 は正になり、オーバーフロー。
ハードウェア的にはMSBへのキャリー入力とMSBからのキャリー出力が異なるときにオーバーフローが起きますが、初心者は符号のルールで十分です。
桁あふれとキャリーの扱い
2の補数の計算では、最上位からの最終キャリーは結果に含めません。
キャリーが出ても正しい場合(例: 7+(−3))があり、逆にキャリーが出なくてもオーバーフローする場合(例: 127+1)があります。
「キャリーの有無=オーバーフロー」ではない点に注意してください。
オーバーフローの有無は前節の符号ルールで判断します。
よくあるつまずきと対策
ビット幅を固定する
最初にビット幅を決め、その幅の中で反転や足し算を行うことが重要です。
幅を意識しないと、同じビット列でも意味が変わります。
例えば11110011は8ビットなら−13ですが、4ビットへ勝手に切り詰めると0011で+3になってしまいます。
計算の前に「8ビットでやる」などルールを固定しましょう。
符号拡張ミスに注意
負の数を広い幅にするときは左を1で埋める符号拡張を行います。
0で埋めると値が変わり、重大なバグにつながります。
例えば8ビットの−5(11111011)を0埋めして00000000 11111011にすると+251になってしまいます。
負の値の拡張にゼロ埋めは厳禁です。
16進表示と2の補数の関係
16進数は2進数4ビットごとにまとめただけの表示です。
16進でFが並ぶ見え方は、上位ビットが1で埋まっている=負の数であることが多いと覚えると直感的です。
ただし、解釈にはビット幅の前提が必要です。
表の前後に空行を入れます。
| 値(8ビット想定) | 2進 | 16進 |
|---|---|---|
| −1 | 11111111 | 0xFF |
| −2 | 11111110 | 0xFE |
| −128 | 10000000 | 0x80 |
| 0 | 00000000 | 0x00 |
| 127 | 01111111 | 0x7F |
同じ0xFFでも、8ビットなら−1、16ビットなら+255ではなく−1(0xFFFF)になります。
幅が違うと解釈も変わります。
プログラミングでの注意点
ほとんどの言語の整数型は2の補数です。
右シフトが符号を保つかどうかは言語や演算子で異なります。
算術右シフトは1で埋めて負の値を保ち、論理右シフトは0で埋めます。
また、表示関数が負の値を16進で出すときは「−0x1」形式か、2の補数のビット列を見せるかが言語で違います。
ビット列として見たい場合はマスクして幅を固定(例: 8ビットなら255とのAND)してから表示すると混乱が減ります。
言語ごとの仕様を確認することが大切です。
範囲チェックのコツ
加算や乗算でのオーバーフローは事前に防ぎます。
同符号同士の加算で結果の符号が変わるなら溢れているという簡易チェックが有効です。
型変換や縮小(例: 32ビット→8ビット)のときは、対象範囲(−128〜127など)に入っているか確認してから代入します。
安全に扱うにはより広いビット幅で計算してから最後に縮める、または言語が提供する安全な演算API(例: 例外を投げる加算、飽和加算)を使うと安心です。
まとめ
2の補数は、負の数を自然な足し算で扱えるようにする実用的な表現です。
ポイントは次の通りです。
ビット幅を必ず決め、負の数は反転+1(または2^n−|x|)で作り、読み取りはMSBの0/1で正負を判断します。
計算は通常の加算で行い、最終キャリーは結果に含めず、同符号加算で符号が変わったらオーバーフローです。
符号拡張と16進表示は幅の前提が重要で、ゼロ埋めや幅の取り違えは典型的なバグです。
この記事の手順とコツを押さえれば、2の補数の表現と計算を自信をもって扱えるようになります。
