標準TTLだけ(!)でCPUをつくろう!(組立てキットです!)
(ホントは74HC、CMOSなんだけど…)
[第14回]

●DAAの続き(その2 減算に対しては?)

前回のDAA(十進補正)の説明は、直前に行われた計算が加算であった、と仮定してのお話でした。
もしも直前に行われた計算が減算であったならば、その結果に対する十進補正は、加算であった場合に対するそれと同じでよいのでしょうか。それとも違う処理が必要になるのでしょうか?

加算のときと同じようにして、そのことを確かめてみます。
ここでも加算のときと同じように、直前に行われた減算がBCD数同士に対して行われたことを前提とします。
減算の場合には、さらに条件が加わります。
直前に行われた減算によって、結果が負(マイナス)になっていないこと、という条件です。
じつはこのことは加算の場合でも同じで、BCD数の計算は正の数同士でおこないます。BCD数に負数の概念はありません(と思います)。余り自信はありません。あるのかも知れませんが、DAAの十進補正の方法を調べてみると、どうも正の数のみを対象にしているように思われますので、多分そうだと思います。

とにかく(確たる根拠はありませんが)ここでは、直前に行われた減算の結果も正の数であった、という前提で考えを進めます。

●ところで、余談ですが、2進数、16進数には符号付の数と符号無しの数があります

また少し、余談になりますが、2進数と16進数の負数について、少しお話をします。
2進数には符号付2進数と符号無し2進数があります(16進数でも同じ)。しかし、2進数も16進数も、10進数のように、−12345というように負号をつけて表現するのではなく、見かけ上は、符号付でも符号なしでも同じ表現の数です。

十進数はマイナスの数は絶対にマイナスの数です(そんなん、当たり前や)。
ところが、2進数(16進数)は、同じ数が、ある場合には符号無しの数(通常扱っている正の数)であり、ある場合には符号付の数(+と−の区別がある)になるのです。

たとえば、11111111(FF)という数は、十進数に直すと普通は255になります。
しかし、この2進数を、「符号付の数」である、とプログラマが勝手に宣言すると、不思議にも、十進数の−1に化けてしまうのです。
そんな、あほなー、と言われそうですけれど、本当なのです。
2進数の11111111(16進数のFF)を255として扱うか、それとも−1として扱うか、は「プログラマの勝手」に任されていますが、しかしそれはでたらめではなくて、2進数の約束事であると同時に計算のルールとしても矛盾のない取り決めなのです。

「そんな、ばかなことは、納得がいかん!」
という方のために、例をもって説明をいたします。

十進数の計算で、4−5は−1です。
そこで同じことを8ビットの2進数(16進数2桁)で行ってみます。
2進数の引き算も十進数の引き算のルールと同じです。
ビットごとの引き算で、0−0=0、1−0=1、1−1=0、ですが、0−1はできませんから、上のビットから桁下がりして(つまり2を借りてきて)そこから1を引きます。つまり、0−1=1(and 上位桁からのborrow)というルールです。

このルールを理解するために、まず十進数の18−6=12という計算を2進数(16進数)でやってみます。十進数の18は16進数の12です。

 0001 0010    18(16進数の12)
 0000 0110(−   6(16進数も6)
 0000 1100    12(16進数のC)
      
ビット3とビット4で上位桁からのボローが発生しています。

計算のルールはわかりましたか?
では、4−5=−1の計算を2進数(16進数)でやってみましょう。

 0000 0100    4(16進数も4)
 0000 0101(−  5(16進数も5)
 1111 1111    −1(16進数はFF)

この計算では、上位バイトからのボローが発生するためCフラグが立ちます。

こんな計算は、どうしても、納得がいかん!これは手品だ、どこかにタネがあるに違いない!
と思われる方がいらっしゃるかも知れませんが、これはコンピュータの世界の約束事なのです。
人間の世界では255が−1になったり、−1が255になることは決してありませんが、コンピュータの世界では、11111111(FF)はふつうは255という符号無しの数として扱うけれど、(あなたが、そうしたければ)−1として扱ってもよい、という約束事があるのです。
2進数の11111111は十進数の255ですけれど、それを十進数の−1だと言っても決して間違いではなくて、両方とも正しいということなのです。

●2の補数(7月22日追記)

一晩寝たあと、本日あらためて読み直して見て、こういういいかげんな書き方をすると、世の中を惑わせることになって、非常によろしくない、ということに気がつきました。
もしも情報技術科の学生や生徒が私の文を読んで、宿題のレポートに11111111を−1にするのは「プログラマの勝手」である、などと書いたりすると、先生に叱られてしまいます。

ことのついでですが、どこかの省のお役人がインターネットの記事を丸写しにして、国会の資料として提出した、などということがあったようですが、レポートの丸写しはよくないですよ、学生生徒諸君。
参考にするのはおおいに結構。しかし、必ず自分の頭で理解することが必要です。
特に科学の分野では、書かれていることが本当にそうなのか(その事実や論理に間違いや欠陥はないか)を、常に、自分で確認する(実際に実験するなどして体験する)ということが最重要だと思います(これは科学の分野だけに限ったことではない、かもしれませんが)。

どうも、いつも余計な話になってしまい、つい脱線してしまいます。
11111111が−1であるというのは、コンピュータの世界の約束事である、と書きました。

人間の世界の約束事というのは、ときとしてあいまいな根拠にもとづいています。法律などはその最たるものでしょう。
しかしコンピュータがもっとも苦手なのは、まさにその「あいまいな考え方」なのです。
とにかく理屈通りでなければ承知してくれません。
したがって11111111は実は−1なのだよ、とプログラマが勝手にコンピュータに宣言したとしても、そのことが実際に計算を行う上で矛盾が無いものでなければ、正しく計算してはくれません。
2進数というのは、コンピュータのために使われている数の概念ですから、11111111が−1である、ということにもはっきりした根拠が必要になります。

その根拠になるのが、補数という考え方なのです。
補数というのは反対の数という意味です。

反対の数?

難しいことではありません。
2進数の1と0をひっくり返せばよいのです。
たとえば、01010111の補数は10101000です。

しかしこの補数は2進数の負数を考えるのには役に立ちません。
うーん、近いのだけれど、もう一息のところでうまくいきません。
ちょうど+の電気と−の電気を合わせると、0になってしまうようなうまい数の組み合わせはないものか。

1と−1を足すとちょうど0になります。
00000001の補数は11111110です。
足しても残念ながら0にはなりません。
00000001+11111110=11111111になってしまいます。これではコンピュータは納得してくれません。
もう一息、ほんの少しなんだけど…。

ということで、一味違う「補数」が考え出されました。
00000001+11111110=11111111にさらに1を加えると、11111111+00000001=00000000になるではありませんか!(ただし上位桁にキャリーが発生します)。
補数に+1を加えた数を、元の数のマイナスの数だと考えたとすると、元の数と、この新しい「補数」を足せば、ちょうど0になるので、都合がよいではないか!

●「1の補数」と「2の補数」

そこで、もともとの補数を「1の補数」と呼ぶことにし、その「1の補数」にさらに+1を加えた数を「2の補数」と呼んで区別するようにしたのです。そしてこの「2の補数」というものを、元の数の負数である、と決めたのです。

以上の説明のように、「2の補数」は、まずもとの数を各ビット毎に1と0を反対にします(「1の補数」を作る)。
たとえば00000101(10進数の5)の「1の補数」は、11111010になります。
つぎにそのようにしてつくった「1の補数」に1を加えます。
11111010+1=11111011です。これが「2の補数」です。11111011は10進数の5に対する「2の補数」ですから、11111011(16進数のFB)は10進数の−5である、ということになります。
念のため、2進数の加算法で確かめてみます。

 0000 0101
 1111 1011(+
 0000 0000

間違いなく加算の結果は0になります。
この加算によって上位桁へキャリーが発生します。
このキャリーは、この計算で扱っている数が、8ビット(16進数2桁)ではなくて、16ビット(16進数4桁)とか24ビット(16進数6桁)というような多桁の数であるような場合に上位桁まで計算効果を伝えるために必要になります。

●16ビットの2進数

参考までに多桁における2進数の負数についても、少し触れておくことにします。
いままでは8ビット(16進数2桁)の数について考えてきました。しかしそれだけでは桁数が不足する計算もたくさんあります。
そこで今度は8ビットではなくて、16ビット(16進数4桁)で1つの数を表す場合を考えてみます。

さきほどの10進数の5は、8ビットの数ならば、00000101(16進数の、05)ですが、これを16ビット(2バイト)の数で示すと、
00000000 00000101(16進数の、0005)になります。
この場合の、負数についても8ビット(1バイト)のときと同じに考えることができます。

まず「1の補数」を考えます。
00000000 00000101の各ビットの1と0を逆にすると「1の補数」になります。
11111111 11111010です。次にこの数にさらに+1を加えると「2の補数」の出来あがりです。
11111111 11111011が10進数の−5に対する16ビット表現の2進数です(16進数は、FFFB)。

●16ビットの計算

16ビットの数を加算したり減算したりするときに、8ビットのコンピュータは一度に計算を完了させることはできません。8ビット(1バイト)ごとに計算を行います。計算は下位桁から行います。
さきほどと同じように、16ビットの5と−5を加算してみます。

まず最初に下位8ビットについて、加算をおこないます。この部分はさきほどの8ビットの数の加算とまったく同じです。

 0000 0101
 1111 1011(+
 0000 0000

結果は0000 0000になって、上位桁(上位バイト)へのキャリーが発生します。さきほどの8ビットの説明で触れたように、このキャリーが重要な働きをします。
上位8ビットの加算を行います。

 0000 0000
 1111 1111(+
         1(+ …下位桁で発生したキャリーを加算する
 0000 0000

下位桁で発生したキャリーを上位桁の加算に伝えることで、16ビットの場合にも、
0000 0000 0000 0101 …10進数の5に相当する16ビットの2進数(16進数では、0005)と
1111 1111 1111 1011 …10進数の−5に相当する16ビットの2進数(16進数では、FFFB)とを加算して、0を得ることができます。
この上位桁へのキャリーは、その計算が最上位桁に対して行われた結果発生した場合には、使われないで捨てられてしまいます。

●2進数の、正の数と負の数

私の場合、一度お話が脱線してしまうと、なかなか本線には戻れなくなってしまいます。
もう少し、脱線したまま、お付き合いを願います。

以上の説明のとおり、「2の補数」というものを考えることで、うまく負数を表すことができるようになりました。
しかし、ただそれだけでは、じつは困ることが出てくるのです(このあたりが、さきほど私が、「科学には十分な検証が必要である」と言った所以です)。
00000101(十進数では5)の負数は、その「2の補数」、11111011(十進数の−5)でよいのだ、ということを説明しました。
本当に、「2の補数」でよいのでしょうか?

十進数の129と−129を2進数でそれぞれ表してみます。
十進数の129は2進数では、10000001です。
すると−129は、10000001の「2の補数」ですから、さきほどのルールでは、まず「1の補数」が、01111110なので、「2の補数」はさらに+1を加えて01111111になります。これが2進数の−129です。
加算して確認してみます。

 1000 0001
 0111 1111(+
 0000 0000

間違いありません。01111111は確かに−129です。

ちょっと、まったぁ!
世の中にたくさんいる悪い人たちは、こうやって善良な市民をだましてしまうのです。皆さん、だまされては、いけません!

01111111は、+127ではありませんか!

すると、+127の負数は、01111111の「1の補数」が10000000だから、さらに+1を加えて「2の補数」を求めると、10000001(!)、これって、+129じゃ、なかったの?
いったいぜんたい、どうなっているのっ!はっきり説明してよっ!説明できなかったら、警察を呼ぶからねっ!

うう…。
ですから、さきほど、それは2進数の約束事で、同じ数が+の数にも−の数にもなる…と、説明いたしました。あ、ですから、ある場合には+127ですけれども、それを−129と考えてもよい、と…。さすれば−127が+129でも一向に構わないのでは、と…。

そーか、そーか、そーだった。それならば、筋が通る。よろしい。許してあげましょう。…って、ごまかされてはいけません。

●符号付の2進数には「符号ビット」というものがある

じつは、2進数の負数にはもうひとつ大事な約束事があります。
それは、その数を符号付の数(つまり+と−の区別がある数)として扱うときには、最上位ビットを符号ビットとみなす、という約束なのです。
ここでいう最上位ビットとは、その数が8ビットなのか16ビットなのか(あるいはもっと多いビット数なのか)で異なってきます。
その数が8ビットの数ならば、ビット7が符号ビットですが、16ビットの数の場合にはビット7ではなくて、ビット15(上位バイトのビット7)が符号ビットになります。

いままで説明に使ってきた数で説明します。

 0000101          8ビットの2進数の5(16進数では、5)
 1111011          8ビットの2進数の−5(16進数では、B)
 0000000 00000101 16ビットの2進数の5(16進数では、005)
 1111111 11111011 16ビットの2進数の−5(16進数では、FFB)

赤で示した最上位ビットが符号ビットなのです。
符号付2進数ではこの最上位ビットが1のものを負数、0のものを正数(および0)であると定義して扱っています。
この約束に従えば、最上位ビットは数の大きさを示すビットとしては使用できなくなりますから、8ビットの符号付の数は実質7ビットの大きさの数しか扱えないことになります。
つまり符号付8ビット数で扱える正の数は00000001〜01111111(16進数表現では、01〜7F。十進数の0〜127)で、負の数は
11111111〜10000000(16進数表現では、FF〜80。十進数の−1〜−128)ということになります。
16進数表現では、その数が符号付の数であった場合には、その最上位桁が0〜7のときが正の数(および0)で、8〜Fのときが負の数になります。

そこで、さきほどのインチキ問題です。
どこにインチキがあったかと言いますと、+129は符号付2進数では、8ビットでは表現できないところを、ごまかして、そのところだけ、「符号無し2進数」を借用しているところにあったのです。
+129と−129は16ビット(または12ビット)で考えれば、矛盾なく計算することができます。

16ビットで考えた129は、0000000 10000001(16進数では0081)なので、その「2の補数」は、
1111111 01111111(16進数ではFF7F)になります。これが符号付2進数の−129なのです。

 000 0000 1000 0001
 111 1111 0111 1111(+
 0000 0000 0000 0000

加算するとちゃんと0になりますし、1111111101111111は−129以外に、16ビットで表現できる符号付の数は存在しませんから、さきほどのインチキの例のようにおかしな矛盾は発生しません。

●マイナスのマイナスはプラス、だが…

まだまだ脱線は復旧しません(この勢いではそのうち、地球の裏側まで行ってしまうのでは…!)。

10進数の計算では、マイナスのマイナスはプラスになります。
さきほどのインチキな説明では、この辺に矛盾がでてきました。
では、符号ビットを考慮した符号付の2進数では、矛盾なく説明できるのでしょうか?

いままで考えてきた「2の補数」の考え方は、なんとなく、正の数に対して、その数の負数が「2の補数」である、とだけ考えているように思えます。
では、負数の「2の補数」を考えることはできないのでしょうか?

考えてみましょう。
01111111(十進の127)の「2の補数」は10000001(十進の−127)でした。
そこで、10000001の「2の補数」を考えてみます。
まず「1の補数」を求めます。01111110です。
これに+1を加算すれば、「2の補数」が求まります。01111111です。
これは十進の127に相当する2進数でした。
確かにマイナスのマイナスはプラスになっています。
めでたし、めでたし…なのですが、例外があります。

まず、0についてですが、00000000の2の補数は、やっぱり00000000になってしまいます。でもこれは、マイナスゼロはプラスゼロと同値、と考えられますから、まっ、これでいっか、ということにしておきましょう。

問題は−128です。
−128の「2の補数」を求めてみます。
−128は8ビットの符号付2進数では、10000000です(なぜか?−127は10000001ですから、これから1を引いた10000000は−128なのです。うーん。どうも、どこかでだまされているような…)。

この際そういうことは考えないで、人の言うことは素直に信じましょう。
まず「1の補数」を求めてみます。01111111になります。次にこの数に+1を加算して、「2の補数」を求めてみると…。
10000000!
あれえ!またもとの−128に戻ってしまいました。

−128をマイナスしたものは+128なのですが、よくよく思い出してみると、8ビットの符号付2進数で表すことのできる正の数は+1〜+127でしたので、+128は8ビットの符号付2進数では表現できないのです。
そこを無理やり押し通そうとすると、このようなおかしなことがおきてしまいます。
うーん。この仕組みを最初に誰が考え出したのか知らないが、実に良く出来ている!と思わずうなりたくなります。
8ビットの符号付2進数の中で10000000(十進数の−128、16進数の80)だけは別格なのです。
16ビットの符号付2進数では10000000 00000000(十進数の−32768、16進数では8000)が別格ということになります。

8ビットの符号付2進数の計算で、無理やり
0−(−128)
という計算をさせると、コンピュータが怒ってしまいます。できないよー!というわけです。
オーバーフローフラグが立ちます。

●まだ、納得がいきません

うーん。まだ、納得がいかないなぁ…。
符号付2進数の最上位ビットが符号ビットで、たとえば8ビットの数の場合、ビット7が1のときに、その数はマイナスの数だというのでしょう?
だったら、+127は2進数では01111111なのだから、−127はその最上位ビットを1にして、11111111になるんじゃないの?
どうして11111111が−1で、−127は10000001なの?そんなの、おかしいよー。

失礼しました。説明不足でした。
符号付2進数は最上位ビットを符号として使用します。
ということは数の大きさに使用できるのは、8ビットの符号付2進数の場合には1ビット減るので、7ビットになります。
表現できる数は0000000〜1111111の範囲になります。
正の数(および0)の場合には、そのまま十進数の0〜127になります。

しかし、最上位ビットが1の場合には、その下にある0000001〜1111111はそのまま十進数にあてはめるのではなくて、ある数が0から引かれた結果がここに示されている、と考えるのです(0000000ではなくて、0000001としたのは、すでに説明した−128に対する配慮からです)。
すると1111111は127ではなくて、7ビットで表現した0000000−0000001の計算結果であるということになります。
同様に、0000001は0000000−1111111の結果であることも理解できると思います。

符号付2進数の負の数をこのようなものにすることによって、符号ビットを計算対象から除外せずに、数の最上位ビットとして、そのビットを含めて、2進数の加算や減算を行っても支障が生じない、という、論理の一貫性につながっているのです。

じつは、符号付2進数の最上位ビットが単なる符号なのか、それとも数そのものの一部なのか、という疑問に対しては、私自身、よくわかっていません。
私は、数そのものの一部であると同時に、数の正負も決定しているビットなのではないか、と思っているのですが…。

●本題に戻ります。BCD数の減算と2進数の減算の結果の違いについて整理してみましょう

やっと本題に戻ります。
加算のときと同じようにBCD数同士の減算をするべきところを2進数として減算を行うと結果はどうなるかを、1桁同士の減算について表にしてみました。BCD数同士の減算なのですから、0〜9−0〜9の範囲の計算ということになります。

引かれる数
引く数
A
A
A
A
9&borrow→ A
10進数


表の一番上の1行が引かれる数0〜9です(黄色)。表の左端の1列が引く数です(緑色)。
左行の一番下がAになっているのは、引く数が9のときに、さらに下位桁からのボローがある場合を示しています。この場合のみ引く数が結果的にAになります。
なお引く数が0の行の計算には、下位桁からのボローはありませんが、その他の行は、下位桁からのボローがある場合も含めています(たとえば、1の行は、引く数が1で下位桁からのボローが無い場合と、引く数が0で、下位桁からのボローがある場合をあわせています。ともに結果が同じになるからです。他の数についても同様です)。
黄色の行と緑色の列ではさまれた範囲が計算の結果の値(16進数)です。

着色されていない範囲の計算は、BCD数の計算でも2進数の計算でも、結果が同じになります。ですからこの範囲は補正をする必要はありません。
水色で着色された範囲はBCD数としての減算と、それを2進数だとして行った減算とで結果が異なっていることを示しています。
一番下の行に、水色で着色された範囲の数の全てが表示されていますから、その下の行に、2進数の減算ではなくて、BCD数−BCD数=BCD数というように、十進数としての減算を行ったときの結果を表示しました。

たとえば水色7の下は1になっています。
水色の7の元になる減算は、0−9と、1−9−ボロー(1−A)です。
十進数の計算なら、上位桁から10を借りてきて(上位桁へのボローが発生して)、結果は1になります。ということを示しています。
また水色8の下は2になっています。
水色の8の元になる減算は、0−8、0−7−ボロー(以上2つはまとめて、0−8にしてある)と、1−9、1−8−ボロー(以上2つもまとめて、1−9にしてある)と、2−9−ボロー(2−A)です。
十進数の計算なら、上位桁から10を借りてきて(上位桁へのボローが発生して)、結果は2になります。ということを示しています。

したがって、減算の場合の補正は、水色の最下行の16進数をその下の16進数(加算の場合とは異なり、たまたま10進数とも一致しますが、ここも扱いは16進数です)に補正する作業である、と言えます。

ところで加算の場合と同じように、着色付の6〜9と着色されていない6〜9があることがわかります。
表をよく見ると、減算の場合には着色されている範囲の計算は全て上位桁へのボローが発生する計算であることがわかります。
つまり、引かれる数<引く数という関係が成り立っています。

ここで注意しておきたいのは、あくまでこの関係は、この1桁(4ビット)の数についてのみ成立している関係で、他の桁も含めて、結果が負数になる、という関係を意味しない、ということです。
すでに説明しましたように、DAA(十進補正)の直前に行われたBCD数同士の加減算は、その計算を十進数の加減算として行った場合に、足される数(または引かれる数)も足す数(または引く数)も、そしてその結果の数もすべて正の数である(厳密には符号無しの数である)、という前提条件を忘れないようにしないといけません。

とにかく、上位桁へのボローが発生しているか否かが、減算の場合の十進補正を行うか行わないかの判断の第一の条件になることがわかります。
さらに結果の数をよく見ると、6〜Fまでに限られていることもわかります。

説明の途中ですが、本日は時間がなくなってしまいました。
この続きはまた次回にいたします。
2008.7.21upload
2008.7.22加筆訂正

前へ
次へ
ホームページトップに戻る