コンピュータの動作原理（４）減算回路


> もしもそれが減算のように歩き、減算のように鳴くのなら、それは減算である
> がんばれ減算。

コンピュータは減算
----

前回は真理値表を論理回路に変換する手法について解説しました。
コンピュータの動作原理は論理GATEを説明したら次は半加算器→全加算器と進め、減算は２の補数表現を使う、というのが常道ですが、ちょっと視点を動かしてみましょう。
***コンピュータは減算です***。
減算があれば加算 A+B は A-(0-B) に変形すればいいのです。減算だけあれば出来てしまいます[^1]。
減算から出発することによって２の補数表現を自然に導く事ができます。
また減算は結果から2つの数の大小関係を知ることが出来ます。

ここではまず1ビット同士の減算回路を作り、それを8つつなげて8ビット減算器にします。そのあとで負の数について考察します。

もちろんコンピュータは計算などしませんから、**減算になるように真理値表を作り**ます。

まず1ビット同士の減算を考えてみましょう。0-1の時、筆算なら引けないので上の桁から借りてくる、という方法がありますが、

　引けなかった時に上の桁から借りるのではなく、最初から上の桁から借りた1を用意して、2桁の10からの減算にする

としてみます。こうすると、筆算にある「引けなかった場合」について考える必要がありません。つまり、上の桁から借りたかどうかの場合分け、とは考えなくても良くなりますし、上の桁がそれより上から借りられなかったらさらに上の桁から…　のように考える必要もありません。
上の桁がさらに上の桁から借りて来るかどうか、については上の桁の回路で同じように考えればよいのです。
つまり、0-1と1-0は同じ考えになります。

※「減算は繰り下がりの処理が煩雑だからCPUは減算が苦手」とか「繰り下がりの処理が煩雑」とかいう解説もありますが、繰り上がりは大丈夫なのに繰り下がりが煩雑なわけ無いです。どちらも考え方は同じだし、回路もほぼ同じです。それにたとえ煩雑だとしても、一度回路にしてしまえば煩雑とか関係なくなります。

上の桁から借りた1(上の桁なので10)と引かれる数 － 引く数 → 結果
10-0 → 10
10-1 → 01
11-0 → 11
11-1 → 10
結果の一の位はそのまま減算の結果になります。10の位は上のケタの借りを使ってしまったら0、必要なかったら1です。

ここのままでは、下の桁との貸し借りが結果に反映されないので、半減算器とでも言う回路です。
複数桁の場合、下の桁に貸したかどうかも式に入れなくてはいけません。
そうすると以下の式になります。

　減算結果＝（上の桁から借りた10と引かれる数） － 下の桁に貸した1が残っているかどうか（残っていなければ1を引く） － 引く数

これで、下の桁に貸せるかどうかも含んだ結果が出て来ます。

※もし、一番上の桁がさらに上の桁から借りた1を返せなかった場合は、それは結果が負になった、ということです。
　負の数については後ほど、結果のビットパターンを素直に受け入れて考察します。

真理値表と回路
----
引かれる数が0、引く数が1、下の桁には貸したままで返ってこなかった(=0)、の場合について考えてみます。
・上から1を借りて引かれる数と一緒にする　10
・下に貸したぶんを引く 01
・そこから引く数1を引く　00
で結果は00です。
この考え方で真理値表を作ってみましょう。

| 引かれる数A | 貸しu |引く数B |→| 上に返すr | 結果Y |
|:-:|:-:|:-:|:-:|:-:|:-:|
|0|0|0||0|1|
|0|0|1||0|0|
|0|1|0||1|0|
|0|1|1||0|1|
|1|0|0||1|0|
|1|0|1||0|1|
|1|1|0||1|1|
|1|1|1||1|0|

　※「貸し」は下に貸して返ってこなかったら0です
　※上から借りた1（↑から借りたので10）はどの場合でも同じなので真理値表では省略します。

YはAが1の時はuとBのXORでAが0のときはそれの反転ですね、それからrはBを反転させてみるとAが0の時はu and !BでAが1の時はu or !Bです。そう思って全加算器の回路と見比べると面白いかもしれません。

では、これを回路にしてみましょう。下の桁への貸し、上の桁からの借りも含む1ビット同士の全減算回路です。上の真理値表そのままに作りました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136856/881cd4e0-b6eb-ad7f-ceeb-e354a3a59f75.jpeg" width=340px>

[^1]: 加算器を作って減算の時は二の補数を使うという解説が一般的ですが、二の補数は符号反転で、つまり減算です。

位取り
----
コンピュータが二値を扱うのに都合が良い事は以前触れました。これを二進数として扱うには「位取り」を導入する必要があります。
二値のモノが8個並んでたとして、そこから数を表現するにはそれぞれが何の位（1の位、10の位、100の位…）なのかをどこかで決まるようにしなくてはいけません。でないと二値のモノ8個で表現できるのは0～8、つまり1がいくつあるか数えるだけ、という事になってしまいます。
数を紙に書いて示す場合、私たちはより左側にある数字が上の位だ、という約束事がありますが、コンピュータの場合はどうでしょう？
それぞれの位は、減算の場合は「引けないから上の位から1を借りてくる」時のつながりでどっちが上の位なのかはっきりします。貸すほうが上の位、借りるほうが下の位、貸す相手がいなかったらそれは1の位です。
つまり、繰り上がりや繰り下がりを隣の位とつないではじめて、二進数だと言うことが出来ます。

8ビット
----
1ビット減算回路を8個並べて8ビット減算回路にしました。
<img src="https://qiita-image-store.s3.amazonaws.com/0/136856/e9aa18cb-faf8-61d1-292d-b64d0a68c4a5.jpeg" width=340px>
一番下の桁（回路図では一番上）はさらに下が無いので貸しません。一番下の貸しは常に１なので電源につないであります。それより上の桁は貸し借りを一番上まで順送りにつないであります。

　※論理GATEはスイッチングにかかる時間のせいで遅延という問題を抱えています。実際のプロセッサは色々工夫して遅延を小さくしています。興味があったら[キャリールックアヘッド]とか[キャリー予測]とかを調べてみてください。

負の数
----
この回路は減算ですから、当然ですが結果がマイナスになる事があります。では、負の数の扱いをどうすればいいでしょう。
負の数を考えなければ、8ビットは0～255の数を表現できます。しかし、負の数を表現しようとすると、この0～255のうち一部を負の数に割り当てなければなりません。ではまず、負の数はどうなるのか実際に0-1をやってみましょう。
<img src="https://qiita-image-store.s3.amazonaws.com/0/136856/1c47d266-2361-c8ed-c524-5a6de071daa9.jpeg" width=340px>
1111 1111がｰ1を示しているらしいです。Bを1,10,11,100と増やしていくと順調にYが（正の数として考えた場合は）減っていきます。

　※8ビットは10進で0~255の256種類を表現できます。1111 1111に1を足した1 0000 0000と0は法256(10進)のもとで合同です。→[Wikipedia 整数の合同](https://ja.wikipedia.org/wiki/%E6%95%B4%E6%95%B0%E3%81%AE%E5%90%88%E5%90%8C)

そうすると正の数と負の数の境界線が気になります。
Aを0000 0000にしたままBを増やしていくと、Bが1000 0000の時にYも1000 0000になります。1000 0000は01111 1111+1ですから128のはずです。このあたりに境界線があるようです。
<img src="https://qiita-image-store.s3.amazonaws.com/0/136856/77d9dcbf-80da-dd31-b369-e36e67c925a1.jpeg" width=340px>
Yの1000 0000は正の数なら十進数で127+1で128ですが、この計算は0000 0000-1000 0000ですからYは十進数でｰ128のはずです。しかしビットパターンの1000 0000が計算の通りｰ128なら0-(-128)でYは128という事になって矛盾があります[^2]。

<img src="https://qiita-image-store.s3.amazonaws.com/0/136856/8887365c-8bd5-2448-9b6b-8e3533be8d16.png" width=200px>

この図のように正の数と負の数の境界線があって、そこを128とするのか-128とするのかを決める必要があります。どちらと決めても矛盾はあるので、より便利な方に決めればいいでしょう。
そこで、この場合です。
<img src="https://qiita-image-store.s3.amazonaws.com/0/136856/f532983c-485c-e459-fe9b-c2252b100c10.jpeg" width=340px>
Aが1000 0000で十進数で128か-128（まだどちらか決めてない）、Bが1000 0001、十進数で-127です。実際に0-127をやってみれば確認できます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/136856/1cb66bc8-dd14-857b-6ef2-1df34939072b.jpeg" width=340px>
1111 1111は0ｰ1の時と同じビットパターンですから、そのまま-1と考えていいでしょう。
　・Aを128と考えると　128-(-127)が-1という結果になります。
　・Aを-128と考えると　-128-(-127)がｰ1で矛盾はありません。
という事で８ビット二進数で1000 0000はｰ128を採用します。
そう考えると、正の数か負の数かを見分ける時に最上位ビットを見るだけでいい、という利点もあります[^3]。

もちろん、負の数を扱わないと判っている場合には8ビットは0～255を表すと考えればいいでしょう。C言語のsigned charとunsigned charの違いはプログラムを書く人が、そしてコンパイラが負の数として扱うかどうか決めたという点だけです。コンピュータの中では特に区別はありません。

[^2]: いわゆるオーバーフローという状態です。同様の事は例えば127-(-127)でも起こります。
[^3]: 二の補数表現といいます。1にマイナス符号を付けたいとします。その場合0000 0001に対しての二の補数である1111 1111を使います。マイナス符号を付ける代わりに二の補数を使うので、二の補数表現です。詳しくは別記事にしましたので、こちらを参照してください→[二進数の二の補数](https://qiita.com/niQSun/items/a3d5e9e7e1fe7b3465b9)

抽象化
----
論理GATEの組み合わせで減算回路を作りました。でもやはりこの回路はスイッチのON/OFFの連鎖だけで出来ています。引き算なんてどこにも無かったでしょ？
けれども、私がそれを減算回路だと思って作り、使う人が減算回路だと思うなら、それは減算回路です。
想いを真理値表に込めて、トランジスタと配線だけのモノに意味を与えるのは、抽象化です。

くどいようですがコンピュータは計算などしていません。今回は加減算を実現するためにまずは減算器を作りました。しかし、それが加算器でも オ・レ・サマー オペレータ回路でもなんでもいいです、肝心なのはどう解釈するか、どう利用する回路にするか、です。

次回は加算器についてちょっと触れたあと、信号の状態を保存するラッチについてです。

一覧へ
----
[コンピュータの動作原理](https://qiita.com/niQSun/items/91396b620638e3f44f19#%E3%83%AA%E3%82%B9%E3%83%88)

----
　※以上の回路は　[SUB_ADD_V2.circ](http://qv80.cocolog-nifty.com/blog/files/sub_add_v2.circ)　に入っています。 [Logisim](http://www.cburch.com/logisim/)用のドキュメントです。xmlですから右クリください。