2の補数表現

Side-A

コンピュータのデータは、詰まるところ ビットの並びでしかないわけで、それを何らかの意味あるデータとして見なす為に、データの取り扱いにルールを設けます。これを型といいます。例えば、一番単純なのはビットの配列を2進数と見なして自然数を表現するとか。文字に番号をつけ、文字列を表現するのにその文字の番号を並べてみるとか。

さて、そんな中で負の数を扱おうとするなら、どうしたら良いでしょうか。一番簡単に思いつくのは先頭の1ビットを正負フラグにしてしまうと言うもの。だけれど、現実にはそうでなくって、負の整数は2の補数で表現することが多いです。それはなぜでしょう。


* * *


話はいきなり飛びますが、ここで昔懐かしい(?)筆算をしてみます。

    73
-)  15
--------

と言う奴です。ところで、筆算には2つの流派があるそうで、この引き算も二通りのパターンがあります。まずは 引き算派

  1. 3から5を引く。(→ 2足りない)
  2. 2足りないので隣桁から1( ×桁の重みで10になる )借りる。
  3. 借りた10から2を引く。(→ 8になる)

次に 足し算派

  1. 5は3より大きいので隣の桁から1( ×桁の重みで10になる )借りる。
  2. 借りた10から5を引く。(→5 になる)
  3. 5と3を足す。(→ 8になる)

みなさんは日頃どちらで計算してるでしょうか?引き算なのだから引き算で解くのは当たり前だとして、なぜ足し算派があるというかというと、ちょっと工夫することで一度も引き算を実行しないで引き算をすることが出来るからです。それは、10の補数を使うことです。

10の補数とは、補って10になる数。たとえば 7 に対する 10の補数 は、3 です。これは殆どの人が覚えてしまっているので、計算するまでもなく答えが求まります(テーブル検索ですね)。だからただの引き算よりも「楽」なハズ。


さて、次の問題。では、コレはどう計算します?

    8
-)  3
--------

これはさすがに大多数の人が「引き算流」で計算しますね。だって、引き算ですもの(^^;
でもここは「足し算流」で計算してみましょう

  1. 何も無いけど、上の位から1(桁の重みで10になる)借りてくる
  2. 3の 10の補数を求める(→7になる)
  3. 7と8を足す(→15になる)
  4. 借りてきた10を返す

どうです?きちんと正解になりました。世の中、10の分解表と足し算があれば、引き算がなくても大丈夫ですね♪ なんかもう、話が見えてきたかしら^^


* * *


さて、実際の 2の補数 の話に入る前にちょっと寄り道です。上記の、「補数を使って楽する筆算」 の話の場合、普通人間の頭の中にある補数変換テーブルは

1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1

だけなので、一桁づつ補数変換します(だって、テーブル変換という裏技つかうから「楽」なんですし)。が、別に全部まとめて補数に変えちゃってもOKです。

   42
-) 23
------

  ↓

   42
+) 77      (23の 100の補数 を足す)
------
  119

で、借りてきた100を返せば 19


これをとりあえず「拡張足し算法」と呼びましょう。ただし、全体の補数を計算する」のが楽になる裏技を人間は持っていませんから、正直こんな方法をとってしまっては素直に計算するより大変です(^^;


* * *


いよいよ、コンピュータの話に戻ります。2の補数とは、要するに 10(bin)の補数。 2の補数で負の値を表現する理由は 「引き算を足し算で表現できるから」がありますが、コンピュータにとって 上記の「拡張足し算法」が 楽な計算方法だというのもあるのです。なぜなら、

  • (一桁づつでない全体の)補数を求めるのが楽
  • 補数化するのに借りてきた「最上位の1」を返す必要がない

からです。

まず最初。2の補数を求める方法は、一番上の桁から「1」を借りてきてそこから元の数を引く・・・なのですが、裏技があります。元の数のビットを反転して1を足す、これです。

10011
  ↓   (ビット反転)
01100
  ↓   (1を足す)
01101

検算
    10011 (元の値)
+)  01101  (裏技で作った補数)
----------
   100000  (借りてくる値を一致する)

ビット反転も1を足すことも、コンピュータにとってはらくちんぽんです。



もう一つは、コンピュータは有限の桁(bit)の中で計算するため、最上位の繰り上がりの「1」は勝手に消えてしまうということ。勝手に消えてしまうことを「最初に借りた値を返した」と解釈すれば、何もしないで借りた値を返す処理の完了です。(すごいい楽!)

※ 8bit整数とする
 42 - 21   → 101010(bin) - 10101(bin)

   0010 1010
+) 1110 1011     ← 0001 0101 の 2の補数
-------------
 1 0001 0101     10101(bin) => 21(dec)
 ↑
この 1は 桁あふれで勝手に消える

じゃあ、桁上がりが生じないときは?といいますと、 つまり借りた数値が返せない場合なので、負の値になります。 で、負の値は2の補数で表現するのでそのままでOK!

※ 8bit整数とする
 21 - 42   → 10101(bin) - 101010(bin)

   0001 0101
+) 1101 0110     ← 0010 1010 の 2の補数
-------------
   1110 1011     1110 1011 は 0001 0101 の 2の補数

(値として最上位ビットが経っている場合、それは 2の補数で表現された負の値だというルールになります)


* * *


2の補数を使って 負の数を表現すれば、

2 + (-3)

なぁんてマイナスの値の足し算も、中身の正負に関係なく、単純に足し算すればいいだけになり、かつそれは コンピュータにとって楽な計算方法でもあるわけです。これは、合理的なのでつかわにゃソンですね。

(シナリオ終了)

Side-B

という講義のネタを新人研修向けに暖めていたのですが、「へぇー、面白いトリビアですね。で、何の役に立つのですか?」と言われてしまいました。

いやだって、ほら、、、、、、あー、、、、、、そうね、そうかもね。・・・というわけで、あえなくボツ。悔しいから blog に乗っけてみました。


型で囲まれていない生の値を見てしまう or 意識しなくては困る 状況でないと役に立たないもん。なるほど、これが C が変人向けって事なのかしら。