5 – 3 はなぜ 2 になる? ― コンピューターは「引き算」を「足し算」でやっている、という不思議の話

スポンサーリンク

はじめに ― ちょっと考えてみてください

前回までの記事で、コンピューターは 半加算器全加算器 を組み合わせて足し算をしている、というお話をしました。

前回までの記事で、コンピューターは **半加算器** と **全加算器** を組み合わせて足し算をしている、というお話をしました。

📖 前回までの記事

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. 1の補数(いちのほすう) — まずはウォーミングアップ
  2. 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. 1の補数を作る(0/1を反転)
  2. そこに 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の補数で計算してみましょう。

手順

ステップやること結果
15 を2進数にする0101
25 の2の補数を作る(反転して+1)1011
375の2の補数足す0111 + 1011
45桁目の桁あふれは捨てる結果を読む

実際の計算

   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の補数(符号付き)
000000
000111
001022
001133
011177
10008-8
10019-7
101010-6
111115-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の補数で行われており、「符号ビット」という考え方の正体になっている

コメント