はじめに ― ちょっと考えてみてください
前回までの記事で、コンピューターは 半加算器 と 全加算器 を組み合わせて足し算をしている、というお話をしました。
前回までの記事で、コンピューターは **半加算器** と **全加算器** を組み合わせて足し算をしている、というお話をしました。
📖 前回までの記事
「
1 + 1 = 10(2進数)」 「3 + 1 = 4も全加算器で桁上がりを処理している」
ここまでは、だいたい想像がつきますよね。論理回路という小さな部品を組み合わせれば、足し算くらいなら作れそうだ、と。
でもここで、ふと疑問が湧いてきませんか?
じゃあ「引き算」はどうやってるの?
「引き算回路」みたいなものを、また別に作っているんでしょうか? AND と OR と NOT の組み合わせで、ゴリゴリと引き算用の回路を……?
実はこの答え、めちゃくちゃ面白いんです。
コンピューターの中で何が起きているかというと、
「引き算」を、わざわざ「足し算」に変換してから計算している
のです。
「えっ、引き算なのに足し算? どういうこと?」って思いますよね。この記事を読み終わる頃には、その魔法の正体がわかります。そして、**「うわ、この発想を最初に思いついた人、天才かよ……!」**と、思わず感動するはずです 🎉
大前提 ― そもそも、なぜ引き算は難しいの?
足し算は、子どもの頃から「桁を揃えて、繰り上がりに気をつけて足していく」と習いました。
引き算も人間にとっては簡単に見えます。5 - 3 = 2、サクッと答えが出ます。
でも、コンピューターの気持ちになって考えてみてください。
コンピューターが扱えるのは、0 と 1 のスイッチだけ。そして、半加算器・全加算器というパーツで、足し算は実現できました。
ここで引き算を実現しようとすると、こんな問題が出てきます:
- 「借りる」処理が必要になる(10進数で
52 - 8をやるとき、上の桁から1借りてくる、あれ) - 負の数をどう表現するか問題(
3 - 5の答えは-2だけど、コンピューターはマイナス記号なんて知らない) - 専用回路を別に作るのは効率が悪い(足し算用と引き算用、2セット必要になる)
エンジニアたちはこう考えました。
「足し算回路は既にあるんだから、引き算もこれで済ませられないか?」
そして、見事にやってのけたのです。その鍵となるのが、これからお話する 「補数(ほすう)」 という考え方です。
ステップ1:日常で体験している「補数」の感覚
まずは身近な例から入りましょう。
「お釣り」の話
100円のジュースを買うのに、500円玉を出したとします。お釣りはいくらでしょう?
そう、400円ですね。
ここで小さな発見があります。
「500 から 100 を引く」 という引き算は、 「100 に 400 を足したら 500 になる」 という足し算と同じ意味です。
つまり、
500 - 100 = 400
↓ 言い換えると
100 + 400 = 500
「引き算は、足したら元の数になるような相手を探す問題」と言い換えられるんです。この「足したら元の数になる相手」を、数学では 補数(ほすう) と呼びます。
もう一つの例:時計
夜中の0時から3時間「前」は何時でしょう?
普通に考えれば 0 - 3 = -3 ですが、時計は「12時間で一周」しますよね。だから答えは 「21時」 です。
これを別の見方で考えてみます。
「3時間戻る」 ということは、 「9時間進む」 のと同じ場所に行きます(3 + 9 = 12 で一周するから)。
ここでも、「引く」を「足す」に置き換えられているのがポイントです。9 は、3 を「12 の世界」で足したら一周する数——これが補数の正体です。
💡 気づきポイント 「ある決まった範囲(500円とか12時間とか)を一周する世界」では、引き算は足し算で表現できます。コンピューターも、まさにこれと同じことをやっているんです。
ステップ2:2進数の世界の「補数」を作ってみる
では、コンピューターの世界——つまり 2進数 で同じことをやってみましょう。
ここで2種類の補数を順番に紹介します。
- 1の補数(いちのほすう) — まずはウォーミングアップ
- 2の補数(にのほすう) — これが本命!コンピューターが本当に使っているやつ
1の補数 ― 「全部ひっくり返す」だけ
例として、4ビット(4桁の2進数)の 0101(10進数で 5)を考えます。
1の補数の作り方は、めちゃくちゃシンプルです。
0 と 1 を全部ひっくり返す。それだけ!
元の数: 0101 (=5)
1の補数: 1010
ひっくり返すだけ。NOT回路(反転回路)一発で作れちゃう。コンピューターにとって超ラクな操作です。
これがどんな意味を持つかを確認してみましょう。元の数と1の補数を足してみます:
0101
+ 1010
------
1111
1111(10進数で15)になりました。これは 4ビットで表せる最大の数(2⁴ – 1 = 15) です。
「足して 1111 になる相手」が1の補数、と覚えてもOKです。
🤔 でも、ちょっとモヤモヤしませんか? 「足して
1111(最大値)」だと、「引き算 = 元の数になる」までは届かないですよね。あと1足りない……。そう、この
1の足りなさが、次の「2の補数」を生むんです。
2の補数 ― 1の補数に「1を足すだけ」
2の補数の作り方は、たった2ステップです。
- 1の補数を作る(0/1を反転)
- そこに 1 を足す
例:0101(=5)の2の補数を作ってみます。
ステップ1: 0101 → 1010 (0と1を反転)
ステップ2: 1010 + 1 = 1011
つまり、5 の2の補数は 1011 です。
何が嬉しいんでしょう? 元の数と2の補数を足してみます:
0101 (=5)
+ 1011 (5の2の補数)
------
10000
10000! 桁が 5桁に増えてしまいました。でも、ここがミソなんです。
コンピューターは4ビットしか持っていないとします。すると、5桁目(一番上の 1)は 桁あふれ(オーバーフロー) で捨てられます。
すると、残るのは……
10000 → (4ビットだから一番上を捨てる) → 0000
✨ おお! ゼロになった!
つまり 4ビットの世界では、こうなっています:
5 + (5の2の補数) = 0
これは、
(5の2の補数) = -5
と言っているのと同じです! 2の補数は、その数の「マイナス版」として振る舞うんです。
💡 気づきポイント 時計が12時間で一周したように、4ビットの世界も
0000から1111まで、10000(16) で一周します。この「一周する世界」では、-5の役割を5の2の補数が果たしてくれるんです。
ステップ3:ついに「引き算 = 足し算」をやってみる
ここまで来たら、もう答えに到達したも同然です。
7 - 5 を 4ビットの2の補数で計算してみましょう。
手順
| ステップ | やること | 結果 |
|---|---|---|
| 1 | 5 を2進数にする | 0101 |
| 2 | 5 の2の補数を作る(反転して+1) | 1011 |
| 3 | 7 と 5の2の補数 を足す | 0111 + 1011 |
| 4 | 5桁目の桁あふれは捨てる | 結果を読む |
実際の計算
0111 (=7)
+ 1011 (=5の2の補数、つまり -5 の役割)
------
10010
↑
この一番上の桁は4ビットからあふれるので捨てる
→ 0010 (=2)
🎉 答えは
0010(=2)!ちゃんと
7 - 5 = 2になりました!
ここで起きたことを、改めて噛みしめてください。
- 使った回路は、前回までに作った全加算器だけ
- やったことは、5 を反転して 1 を足す(2の補数を作る)ことと、普通の足し算だけ
- 「借りる」処理も、引き算専用回路も、いっさい登場していません
💎 これがこの記事の一番面白いところ
引き算という、一見独立した操作が、「2の補数を作る」というほんの小さな前処理だけで、足し算に化けたのです。
しかも「2の補数を作る」は、0/1の反転 + 1を足す という、これまた足し算回路でできてしまう操作。
足し算回路だけで、足し算も引き算もできる。 これが、コンピューターが「引き算回路を別に持たない」理由です。
ステップ4:負の数の表現にもなっている
ここまで読んで、もう一つ気づくことがあります。
2の補数は、そのまま「負の数」として使える
4ビットの2の補数表現で、各ビットパターンが何を意味するか整理してみましょう。
| ビット | 符号なし | 2の補数(符号付き) |
|---|---|---|
0000 | 0 | 0 |
0001 | 1 | 1 |
0010 | 2 | 2 |
0011 | 3 | 3 |
| … | … | … |
0111 | 7 | 7 |
1000 | 8 | -8 |
1001 | 9 | -7 |
1010 | 10 | -6 |
| … | … | … |
1111 | 15 | -1 |
同じ 1011 というビットの並びが、
- 「符号なし」と思えば
11 - 「2の補数」と思えば
-5
として読めます。ビットパターン自体は同じで、解釈の仕方だけが違うんです。
そして見てください。2の補数の世界では、一番左のビットが 1 なら必ず負の数になっています。これが「符号ビット」と呼ばれる、お馴染みの仕組みの正体です。
💡 気づきポイント マイナス記号(
-)なんていう特別な記号を使わなくても、ビットの並び方の解釈ルールを変えるだけで、負の数を表現できてしまう。コンピューターは記号を持たないかわりに、**「読み方の約束ごと」**で世界を広げているんです。
ステップ5:全体の流れを図で追ってみる
ここまでの流れを、フロー図で見てみましょう。A - B を計算する手順です。
flowchart TD
A["A - B を計算したい"] --> B["B の各ビットを反転する(1の補数)"]
B --> C["反転した B に 1 を足す(2の補数 = -B)"]
C --> D["A と (-B) を全加算器で足す"]
D --> E{"4ビットを超える桁が出たか?"}
E -->|出た| F["桁あふれを捨てる"]
E -->|出ない| G["そのまま読む"]
F --> H["結果が A - B"]
G --> H
classDef default fill:#BBD4F0,stroke:#1F4E79,stroke-width:1.5px,color:#0B2545
classDef decision fill:#FFE699,stroke:#806000,stroke-width:1.5px,color:#0B2545
class E decision注目してほしいのは、真ん中の青い四角(処理ノード)が、すべて「反転」か「足し算」だけで構成されていることです。
つまり、必要な部品は:
- NOTゲート(反転するため)
- 全加算器(足し算するため)
これだけ。新しい部品はゼロです。
ちょっとした補足 ― 完璧じゃないところ
「素晴らしい!じゃあ全部これで解決だ!」と言いたいところですが、現実には少し罠があります。
罠1:表現できる範囲に限界がある
4ビットの2の補数では、-8 から +7 までしか表現できません。
例えば 7 + 1 をやってしまうと:
0111 (=7)
+ 0001 (=1)
------
1000 (=-8 !?)
足し算したのに、結果が急にマイナスになりました。これが オーバーフロー——表現できる範囲を超えてしまった時のバグの一種です。
実際のコンピューターは、こういう時に「オーバーフローフラグ」を立てて、プログラムに「あぶないよ!」と知らせます。
罠2:引く数が大きいときも、桁あふれが起きない
3 - 5 をやると -2 になるべきですが、4ビットだとこうなります:
0011 (=3)
+ 1011 (=5の2の補数 = -5)
------
1110 (=-2、2の補数として読む)
桁あふれは起きませんが、結果の一番左のビットが 1 になっていますよね。これを 「2の補数として」読む と -2。読み方さえ守れば、ちゃんと正しい答えになっています。
「桁あふれを捨てる」というのは、正の結果になる引き算のときだけ起きる現象。負の結果になるときは、桁あふれせず、そのまま負の数として残るんです。
まとめ
ここまでお疲れさまでした! ポイントを整理しておきましょう。
- コンピューターは引き算を直接やらない。「2の補数」を使って 足し算に変換 している
- 1の補数 は、ビットを 全部反転 するだけで作れる
- 2の補数 は、1の補数に 1 を足す だけで作れる
- ある数の2の補数は、その数の 「マイナス版」 として振る舞う(足したら 0 になる)
- だから
A - Bは、A + (B の2の補数)で計算できる - これにより、コンピューターは 足し算回路だけ で四則演算の半分を済ませている
- 同時に、負の数の表現も2の補数で行われており、「符号ビット」という考え方の正体になっている
コメント