シリーズ「コンピュータの計算のしくみ」 第7

回路が「割る」とは?

最後に残った、ひとつ

第 2 回で、残るのは引き算・掛け算・割り算だと書いた。

引き算は、加算器にスイッチを 1 つ足すだけで足し算に化けた。 掛け算は、AND と配線と加算器に分解された。

残っているのは、割り算である。

ここまでの 3 つには、共通点があった。 どれも入力を入れれば、信号が通り抜けて答えが出る。回路は何も迷わない。

割り算だけが違う。 筆算で商を立てるとき、私たちは一度「引けるだろうか」と試している。 引けなければ、書いた商を消してやり直す。

回路に、試させることはできるのだろうか。

引けるだけ、引いてみる

割り算は引き算の繰り返しだ、とも言える。 13 ÷ 3 なら、13 から 3 を引けるだけ引けばいい。

13 → 10 → 7 → 4 → 1。4 回引けて、1 が残った。 つまり商は 4、余りは 1 である。たしかに割り算になっている。

そして私たちはもう、引き算ができる回路を持っている。新しい部品は 1 つも要らない。

ただし、回数を考えてほしい。 引く回数は、商そのものと同じだけ必要になる。

4 桁どうしなら、最大でも 15 回。まあ、待てなくもない。 けれど 8 桁なら最大 255 回。32 桁なら——最大で約 43 億回

第 6 回で、掛け算を「足し算の繰り返し」でやろうとして同じ数を見た。 素朴なやり方は、桁が増えると現実的でなくなる。

そのときの答えは、筆算だった。今回も、同じところに戻ってみよう。

筆算は、なぜ少ない手数で済むのか

十進法の割り算の筆算は、こんな形だった。138 ÷ 6 を書いてみる。

23
6)138
12
18
18
0

答えは 23 である。 さっきのやり方なら、6 を 23 回引かなければならなかった。 けれどこの紙の上で、引き算は2 回しか書かれていない。

やっていたのは、次のことである。

  1. 上の桁から見て、6 は 13 に 2 回入る。2 を立てて、12 を引く。残り 1。
  2. 次の桁 8 をおろして 18。6 は 18 に 3 回入る。3 を立てて、18 を引く。残り 0。

引き算の回数が、商の大きさではなく割られる数の桁数で決まっている。 これが筆算の速さの正体だ。

けれど、ここで私たちは少しずるいことをしている。 「6 は 13 に何回入るか」を、どこかで知っているのだ。

これは九九の逆引きである。 割る数が 2 桁になると、この逆引きはうまくいかなくなる。 「23 は 178 に 8 回くらいかな」と見当をつけて、引いてみて、大きすぎたら消して 7 に書き直す。

小学校で消しゴムを使ったのは、この瞬間だったはずだ。 これが「試す」である。

消しゴムで消している画像

大きすぎたら消す!

2 進法では、試すことが 2 択になる

さて、十進法では、商の 1 桁は 0 から 9 までの 10 通りあった。 だから「何回入るか」を探さなければならなかった。

2 進法では、商の 1 桁は 0 か 1 しかない。

つまり、探すことがない。問いはこう変わる。

「何回入るか」ではなく、「入るか、入らないか

入るなら商のその桁は 1、入らないなら 0。それだけである。

第 6 回で、2 進法の九九は 4 通りしかなかった。 割り算でも同じことが起きている。割り算の九九も、覚えるべき表も要らない。

必要な判断は、たった 1 つに減った。

引けるか?

「引けるか」は、どうすれば判るか

引けるかどうかを知るには、2 つの数の大小を比べればよい。 けれど、比べるための新しい回路を作る必要はない。

引いてみればいい。

第 3 回で、引き算は加算器のスイッチ 1 つになった。 第 4 回で、答えの一番左の桁を見るだけで、プラスかマイナスかが一目で分かることを見た。 一番左の桁を -8 の位と読む、あの話である。 この記事では、その一番左の桁のことを符号の桁と呼ぶことにする。

この 2 つを重ねると、こうなる。

  1. とりあえず引いてみる(加算器にやらせる)
  2. 答えの一番左の桁を見る
  3. 0 なら引けた。1 ならマイナス、つまり引けなかった

「試す」の正体は、これだった。 引いてみて、符号の桁を 1 マスだけ見る。

ここでも、新しい部品は出てこない。

ただし、引けなかったときには後始末がいる。 引いてしまった結果はまちがいなので、元に戻さなければならない。 戻すのは簡単で、引いた数をもう一度足せばいい。

引き戻し法

手順としてまとめる。4 桁 ÷ 4 桁なら、4 ステップで終わる。

  1. 余りを 2 倍して、割られる数の次の桁を 1 つおろす
  2. 割る数 B を引く
  3. 一番左が 0(引けた)なら、商のその桁は 1。余りはその結果
  4. 一番左が 1(引けなかった)なら、商のその桁は 0。B を足して元に戻す
  5. 4 桁ぶん、1 から繰り返す

最後に残った値が、余りである。 この方式を引き戻し法(復元法)という。

なお、1 の「2 倍しておろす」は、筆算で桁を 1 つ右へ進むことにあたる。 第 6 回で 1 桁左にずらすのが 2 倍だった、あれと同じで、2 倍するのは配線をずらすだけだ。 ここもタダである。

押して、確かめる

4 桁 ÷ 4 桁の割り算を、1 周ずつ進められるようにした。 上の 2 段の青いマスが、割られる数と割る数である。

4 桁の除算器 シミュレータ

青いマスを押して 2 つの数を作り、「1 周すすめる」で筆算を 1 行ずつ進めてください

割られる数 A
割る数 B÷
····
余り····

1101 ÷ 0011 = ···· あまり ····

0 / 4 周

「1 周すすめる」を押すと、ここに筆算が 1 行ずつ現れます

はじめから 1101 ÷ 0011(13 ÷ 3)を入れてある。 「引けなければ戻す(引き戻し法)」のまま、いくつか試してほしい。

  • 1101 ÷ 0011(13 ÷ 3)。1 周目は引けない。2 周目だけ引けて、あとは引けない。 商 0100、余り 0001。10 進法で 4 あまり 1 だ。
  • 1111 ÷ 0100(15 ÷ 4)。前半 2 周は引けず、後半 2 周は引ける。商 0011、余り 0011
  • 0111 ÷ 1001(7 ÷ 9)。4 ステップとも引けない。商 0000 で、余りは 0111。 割る数のほうが大きいとき、割られる数がそっくり余りになる。
  • 1100 ÷ 0100(12 ÷ 4)。余りが 0000 になる。割り切れる、とはこのことである。

どの周でも、判断に使っているのは結果の一番左のマス 1 つだけである。 赤くなっていれば引けなかった、白ければ引けた。それ以外は何も見ていない。

先ほどの 1101 ÷ 0011 を、紙の筆算の形で書くとこうなる。

0100
11)1101
11
0001

実際に引き算が起きたのは、4周のうち1周だけだった。 残りの 3 周は「引いてみて、戻した」だけである。

「戻す」のが、もったいない

その戻す作業が、少し気になる。

引けなかった周では、引いて、また足して、元の値に帰ってきている。 行って帰ってきただけだ。4 桁なら、最悪 4 回むだになる。

省けないだろうか。

引く前の値を X 、割る数をBとしよう。引いたら X − B になる。この結果がマイナスだったとする。 この状態でこの次の周にどのような計算をするか考える。

  • 戻す場合:値をX-Bから X に戻す。次の周では 2 倍して次の桁をおろし、B を引く。 結果は 2X + (次の桁) − B
  • 戻さない場合:X − B のまま次の周へ行き、2 倍して次の桁をおろし、今度は B を足す。 結果は 2(X − B) + (次の桁) + B = 2X + (次の桁) − B

同じ値にたどり着いた。

理由は式を見なくても分かる。 戻すというのは B を足すことだ。その B は次の歩で 2 倍されて、2B に育つ。 そこから B を引くのだから、差し引き +B が残る。

ならば最初から戻さずに、2 倍したあとで B を 1 回足せばいい。 「戻してから引く」と「戻さずに足す」は、同じことなのだ。

引き放し法

手順はこうなる。

  1. 余りを 2 倍して、次の桁を 1 つおろす
  2. 前の余りが 0 以上なら B を引く。マイナスなら B を足す
  3. 結果の一番左が 0 なら商のその桁は 11 なら 0
  4. 4 桁ぶん繰り返す
  5. 最後に余りがマイナスだったら、そのときだけ B を 1 回足して直す

最後の5ステップ目が少しわかりづらいかもしれないが、これは「戻す」のと同じ計算である。 先ほどの計算式の例では引く前の値をX,割る値をBとしていた。引くとX-Bとなる。これがマイナスになったのだから、X-BにBを足すことでXに「戻した」のである。

シミュレータで「戻さない(引き放し法)」を選び、もう一度 1101 ÷ 0011 を進めてみてほしい。 商も余りも、さっきとまったく同じ 01000001 になる。 違うのは、途中の余りがマイナスの値を通っていくことだけだ。

この方式を引き放し法(非復元法)という。

1 周でやる計算は、引くか足すかのどちらか 1 回になった。 引き戻し法では、引けなかった周で引く・戻すの 2 回が必要だった。

最後の「マイナスなら 1 回足す」は、4 ステップのあいだに 1 回あるかないかである。 1101 ÷ 0011 では後始末が出るが、1111 ÷ 0100 では出ない。 シミュレータで両方を試すと、出たり出なかったりするのが見える。

比べてみる

2 つの方式を並べる。4 桁 ÷ 4 桁の場合だ。

引き戻し法引き放し法
何周かかるか4 周4 周
1 周でする計算引く(+戻す)引くか足すか、1 回
1 周あたり最大2 回1 回
途中の余りいつも 0 以上マイナスにもなる
後始末要らない最後に 1 回あることがある

何周かかるかは変わらない。違うのは、1 周の中身である。

もっとも、引き戻し法でも「引く前の値を取っておいて、ダメだったらそちらを選び直す」 という作り方はできる。その場合は足し直さずに済むが、代わりに どちらを選ぶかを決める部品が要る。 どこかで手間を減らせば、どこかに部品が増える。第 6 回で見た取引と同じだ。

なぜ、割り算だけ遅いのか

第 6 回で、掛け算には速い作り方があった。 4 本の部分積を加算器で一気に足してしまう、アレイ乗算器である。

あれができたのは、部分積が最初から全部わかっていたからだ。 A と B を入れた瞬間に、4 本とも決まる。だから並べて同時に足せた。 部品を増やせば、時間が買えた。

割り算は、そうはいかない。

2 周目に引くのか足すのかは、1 周目の符号の桁を見るまで決まらない。 3 周目は 2 周目を待ち、4 周目は 3 周目を待つ。

次の一周が、前の一周の答えを待っている。

ドミノが倒れている画像

前が倒れるまで、次は倒れない

だから部品をいくら増やしても、素直には速くならない。 これが、割り算だけが遅い理由である。

いまのコンピュータでも、割り算は足し算より何倍も時間がかかる。 プログラムを速くしたいときに、割り算を避ける工夫が語られるのはこのためだ。

ただし、2 で割るときだけは別である。 第 6 回で 1 桁左にずらすのが 2 倍だったことを学んだ。逆に考えると1 桁右にずらすのは0.5倍(半分)になる。 4 で割る、8 で割るのも同じだ。 2 のべき乗で割るときは、配線をずらすだけで済む。 1周もせずに計算できる。

0 で割ると、どうなるか

シミュレータで、割る数 B を 0000 にしてみてほしい。 ボタンが押せなくなっているはずだ。止めてある。

なぜ止めたのか。

0 を引いても、値は 1 も減らない。 何歩進んでも余りは減らないまま、商の桁はすべて「引けた」になる。 桁を増やしても終わらないし、増やす意味もない。答えがそもそも存在しないからだ。

困るのは、回路はそのことに気づけないという点である。 回路は引いて、符号の桁を見て、商を立てる。それしかしない。 0 で割られていることも、答えが無いことも、回路にとってはどうでもいい話だ。

だから、割る前に B が 0 かどうかを別に調べておく。 0 なら計算を始めさせず、外へ「これは無理だ」と知らせる。 CPU はこれを例外と呼んで報告する。 プログラムで 0 除算のエラーが出て止まるのは、この仕組みが働いているからだ。

第 5 回のあふれと、少し似ていて、少し違う。 あふれは、答えは出たうえで読み方が壊れる話だった。だから XOR 1 つで見つけられた。 0 除算は、答えがどこにも無い。回路の側で見つけられることではないので、 外から止めるしかないのだ。

四則演算が、そろった

第 1 回から数えて、増えた部品を並べてみる。

  • 足す … 加算器(第 1・2 回)
  • 引く … 加算器 + 反転のスイッチ(第 3 回)
  • 掛ける … AND + 配線 + 加算器(第 6 回)
  • 割る … 加算器 + 配線 + 符号の桁を見る目(今回)

四則演算がすべて、加算器と配線とわずかなゲートに還元された。

回路は、数がいくつなのかを知らない。大小を比べることもできない。 できるのは、1 桁ずつ足すことだけだ。

それでも、桁の順番と、次に何をするかの決め方をこちらが与えてやれば、 掛けることも、割ることもできてしまう。 第 1 回で、電卓の中に答えの表が入っているわけではない、と書いたのは、このことだった。

ここまでは、すべて整数の話である。 0011 は 3 であって、3.5 でも 0.75 でもなかった。

小数点は、どこへ行ったのだろう。

参考文献