★マイナスの表現★


人間がマイナスの数を表現する場合−(マイナス記号)を書くだけで、負の値と判断できます。

たとえば、マイナス5と表現したい場合、5にマイナス記号を付けて、

−5

と書きます。

しかし、コンピュータ内部では全ての情報を2進数で扱っているため、マイナス記号のような便利なものは使えません。

そこで、マイナスの値を扱うために様々な方法を考えました。

ここでは、代表的な「補数」について書きます。

具体的な説明が欲しい!という方は、このまま読み進めてください。

簡単な説明で構わない!という方は、「こちら」をクリックしてください。


・具体的な説明


まず、普段皆さんが使っている10進数で説明しましょう。

10進数に対する補数は、9の補数との10補数があります。

10進数に対する9の補数とは、「加算すると各桁の数値が9になる数」です。

例えば、1024(10進数)という数値の9の補数は、「8975」です。

  1024 + 8975 = 9999

4桁同士の加算を行うと「9999」になります。

また、10進数に対する10の補数は「9の補数に1を加算したもの」になります。

例えば、10進数「1024」の10の補数は「8976」です。

  1024 + 8976 = 10000

4桁同士の加算を行うと、1つ桁上がりして「10000」になります。


これが何の役に立つんだろうか?と思いますが、ここからが重要な所です。

これから10進数の減算を行ってみます。

  4567 − 1024

という計算です。

   4567
  −1024
  −−−−−
   3543

答えは、「3543」ですね。


では、1024の10の補数である「8976」を使って計算してみます。

ただし、減算ではなく、加算をします。

   4567
  +8976
  −−−−−
  13543

答えは、「13543」です。


この答えの一番左の桁を無視すれば「3543」となり、減算した結果と同じになります。

一番左の桁を無視することで、「減算が加算で行える」ようになります。

これが重要なのです。

減算が加算で行えるのであれば、減算をする必要がなくなります。

コンピュータでは、計算を行うための装置が内臓されていますが、加算を行う装置さえあれば、減算を行うための装置を設置しなくても良いというメリットになります。


では、2進数で考えてみます。

とりあえず、2進数での加算を説明しましょう。

わかりやすくするために、2bit(2進数2桁)程度で説明します。


・2進数での加算


2進数の加算の方法は分かっている!という方は「こちら」をクリックしてください。


   00
  +00
  −−−
   00

0+0は当然0です。


   01
  +00
  −−−
   01

1+0は1です。


   01
  +01
  −−−
   10

1+1は、2ですから桁上がりしていますね。


   01
  +10
  −−−
   11

1+2は、3です。


   01
  +11
  −−−
  100

1+3は、4ですから3桁になります。

これは、順を追って説明してみましょう。


まず、右端の桁の1+1を行います。

結果は2ですから、桁上がりが発生し下図のようになります。

   1  ←桁上がり
   01
  +11
  −−−
    0

次の桁の計算1+1を行うと2になりますから、また桁上がりが発生し、下図のようになります。

  11
   01
  +11
  −−−
   00

最後に、桁上がりした1を下に降ろして完成です。

  11
   01
  +11
  −−−
  100

10進数は10で桁上がりしますが、2進数は2で桁上がりします


もう1つ、

   11
  +11
  −−−
  110

3+3は6です。

これも順を追って説明します。


右端の桁の計算を行うと1+1で2となり、桁上がりが発生します。

   1
   11
  +11
  −−−
    0

次の桁は1+1+1となり、3ですから、桁上がりして1残ります。

  11
   11
  +11
  −−−
   10

最後に、桁上がりした1を下に降ろして完成です。

  11
   11
  +11
  −−−
  110

では、補数の説明に戻りましょう。


2進数に対する補数は、1の補数2の補数があります。

2進数に対する1の補数とは、「加算すると各桁の数値が1になる数」です。

4bit(2進数4桁)で説明します。

2進数「1001」に対する、1の補数は「0110」となります。

   1001
  +0110
  −−−−−
   1111

このようになります。

2進数に対する2の補数は、「1の補数に1を加算したもの」になります。

ですから、2進数「1001」の2の補数は「0111」になります。

   1001
  +0111
  −−−−−
  10000

先ほど2進数の加算で説明したように、右端の桁から桁上がりが発生し、1桁増えています。


10進数に対する10の補数と同じでしょう?

ということは、2の補数を使えば、加算で減算が出来るかもしれません。


2進数「1101−1001」という計算をしてみましょう。

   1101
  −1001
  −−−−−
   0100

こうなります。


では、2の補数を使って加算で減算を行ってみます。

2進数「1001」の2の補数は、「0111」ですから、

   1101
  +0111
  −−−−−
  10100

となり、一番左の桁を無視すれば「0100」で、減算と同じ結果となります。

10進数に対する10の補数と同じですね。


さて、最後に残った問題は「一番左の桁を無視すれば」という箇所です。

「無視できれば」減算を加算で行うことが出来ますが、どうやって無視するかが問題です。

これまで勉強したことをすべてリンクさせるため、8bit(2進数8桁)でやってみます。


2つの2進数を使って減算してみます。

 0011 0011 → 10進数「51」
 0010 0001 → 10進数「33」

10進数で考えると、51−33=18ですね。

2進数で考えてみます。

   00110011
  −00100001
  −−−−−−−−−
   00010010 → 10進数「18」

合っています。


では、補数を使ってみます。

2進数「0010 0001」の2の補数は、「1101 1111」です。

   00110011
  +11011111
  −−−−−−−−−
  100010010

「1 0001 0010」となりました。


前に説明したC言語のchar型では、「サイズに収まりきらない部分が切り捨てられる」と書きました。

2の補数を使って加算を行うと、8bit同士の加算の結果が9bitとなりました。

  +−+−+−+−+−+−+−+−+
 1|0|0|0|1|0|0|1|0|
  +−+−+−+−+−+−+−+−+
   128 64 32 16  8  4  2  1

char型を使って計算すると、一番左のビットは「切り捨て」られます。

  +−+−+−+−+−+−+−+−+
  |0|0|0|1|0|0|1|0|
  +−+−+−+−+−+−+−+−+
   128 64 32 16  8  4  2  1

結果10進数「18」になりました。

つまり、「無視できれば」と書いてきた部分は、コンピュータを使えば、自動的に「切り捨て」られるのです。

長いこと説明してきましたが、加算することで減算できる「補数」をマイナスの値と考えることがコンピュータ内部で行われているのです。


上で説明した、

2進数「0010 0001」の2の補数は、「1101 1111」です。

という箇所がありますが、

この「1101 1111」を、10進数「−33」と考えるのです。

そうすると、

   00110011 → 10進数「51」
  +11011111 → 10進数「−33」
  −−−−−−−−−
  100010010

となり、一番左のビット「1」を切り捨てると、この計算は、

51 + (−33) = 18

となるのです。

続きは下の「簡単な説明」を見てください。


簡単な説明


マイナスの値を扱うため、いろいろな方法が考えられましたが、「2の補数」と呼ばれる方法を説明します。

※2進数8bitで説明します。


これまで紹介した2進数はすべて符号なし(unsigned)でしたから、各桁の位を表示すると下図のようになります。

  +−+−+−+−+−+−+−+−+
  | | | | | | | | |
  +−+−+−+−+−+−+−+−+
   128 64 32 16  8  4  2  1

符号あり(signed)の場合、マイナスを使用することになりますので、次のように考えます。

  +−+−+−+−+−+−+−+−+
  | | | | | | | | |
  +−+−+−+−+−+−+−+−+
  -128 64 32 16  8  4  2  1

一番左の桁(最上位ビット)の位を−128と設定します。

このビットが1の場合、−128が1個と数える訳です。

※この最上位ビットを「符号ビット」と言い、「1」であればマイナス、「0」であればプラスと考えます。


例えば、2進数「1001 0001」は、

   1の位 が 1個 =    1
   2の位 が 0個 =    0
   4の位 が 0個 =    0
   8の位 が 0個 =    0
  16の位 が 1個 =   16
  32の位 が 0個 =    0
  64の位 が 0個 =    0
−128の位 が 1個 = −128

合計すると、「−111(10進数)」となります。


いくつか数を書いてみます。

 2進数         10進数

 00000000 →    0
 00000001 →    1
 00000010 →    2
  ・
  ・
  ・
 01111110 →  126
 01111111 →  127

 10000000 → −128
 10000001 → −127
 10000010 → −126
  ・
  ・
  ・
 11111110 →   −2
 11111111 →   −1

一番左端の桁をマイナスとすることにより、正の値は「127」までしか表現出来なくなりましたが、負の値は「−1〜−128」まで表現できるようになりました。

他にもマイナスを表現する方法はあるのですが、現在のコンピュータの多くがこの方法を採用しています。

なぜでしょうか?

実は、2の補数を使うことによるメリットがあるのです。


そのメリットとは、「減算を加算で行える」というメリットです。

もし、マイナスの表現方法が無ければ、減算をしなければ数を引くことが出来ません。

 5 − 3 = 2

という具合です。

しかし、マイナスの表現が出来れば、減算の必要はなくなります。

  5 + (−3) = 2

という具合です。

コンピュータでは、計算を行うための装置が内臓されていますが、加算を行う装置さえあれば、減算を行うための装置を設置しなくても良いというメリットになります。


実際にやってみましょう。

簡単な例で、「15−1(10進数)」をやってみましょう。

10進数「15」は2進数8bitで「0000 1111」です。

10進数「 1」は2進数8bitで「0000 0001」です。


2進数で減算してみます。

   00001111
  −00000001
  −−−−−−−−−
   00001110 → 10進数「14」

こうなりますね。


では、「15+(−1)」を2の補数を加算するという方法で計算します。

10進数「−1」は2の補数で「11111111」です。※上記参照

   00001111
  +11111111
  −−−−−−−−−
  100001110

2の補数を使って加算を行うと、8bit同士の加算の結果が9bitとなりました。

  +−+−+−+−+−+−+−+−+
 1|0|0|0|0|1|1|1|0|
  +−+−+−+−+−+−+−+−+
  -128 64 32 16  8  4  2  1

前に説明したC言語のchar型では、「サイズに収まりきらない部分が切り捨てられる」と書きました。

  +−+−+−+−+−+−+−+−+
  |0|0|0|0|1|1|1|0|
  +−+−+−+−+−+−+−+−+
  -128 64 32 16  8  4  2  1

char型を使って計算すると、一番左のビットは「切り捨て」られますので、ちゃんと「14」という結果になっています。


・まとめ


だいぶ遠回りしましたが<sample program col013-01>に戻ります。

char型変数に200を代入したら−56と表示されたというものでしたが、このchar型の表現可能な数の範囲は−128〜127までです。

200は表現できないのですが、なぜ−56と表示されるのか説明します。


char型(符号あり)の場合、内部では2の補数を使ってマイナスの表現を行っています。

  +−+−+−+−+−+−+−+−+
  | | | | | | | | |
  +−+−+−+−+−+−+−+−+
  -128 64 32 16  8  4  2  1

これですね。


ここに200を代入しますが、10進数で考えると代入できるとは思えません。

そこで、10進数「200」を2進数にしてみます。

   1の位 が 0個 =   0
   2の位 が 0個 =   0
   4の位 が 0個 =   0
   8の位 が 1個 =   8
  16の位 が 0個 =   0
  32の位 が 0個 =   0
  64の位 が 1個 =  64
 128の位 が 1個 = 128

合計すると、「200」ですから2進数であらわすと「11001000」となります。


これを上記の2の補数8bitの枠に収めてみます。

  +−+−+−+−+−+−+−+−+
  |1|1|0|0|1|0|0|0|
  +−+−+−+−+−+−+−+−+
  -128  64  32  16   8   4   2   1
   1の位 が 0個 =   0
   2の位 が 0個 =   0
   4の位 が 0個 =   0
   8の位 が 1個 =   8
  16の位 が 0個 =   0
  32の位 が 0個 =   0
  64の位 が 1個 =  64
−128の位 が 1個 =−128

−128 + 64 + 8 = −56

合計すると−56になりました。


結局、コンピュータの中では2進数が使われており、符号ありにするか符合なしにするかは人間が決めることなのです。

例えば、次のプログラムを見てください。


<sample program col016-01>

#include <stdio.h>

int main(void)
{
    char data1 = 200;
    unsigned char data2 = 200;

    printf("data1 = %d\n", data1);
    printf("data2 = %d\n", data2);
}

<実行結果>

data1 = -56
data2 = 200
続行するには何かキーを押してください・・・

変数data1はchar型(signed:符号あり)で宣言されています。

変数data2はunsigned char型(符号なし)で宣言されています。

同じように200を代入し、表示してみると異なった数値が表示されます

要は、コンピュータ内部では「11001000」というビットの列しか存在せず、プログラマが符号あり、なしを変数の宣言時に決めるのです。

このことを頭においていれば、訳の分からない数値が出てきた!と思うことは少なくなります。


ブラウザの戻るボタンで戻ってください。