補数


2進数で負の数(マイナス)を表すときに使用するのに使用されるのが補数である。
補数というのはある数に対し、その数を足すことでけた上がりが発生する最小の数と、その数を足すことでけた上がりが発生しない最大の数があります。基数N(10進数なら“10”、2進数なら“2”)の場合、前者をNの補数、後者をN - 1の補数といいます。(補数の詳細な説明は省きます。)

2進数の場合は、非常に簡単で各けたの0と1を入れ替えたものが1の補数で、1の補数に+1したものが2の補数になります。

コンピュータで負の数を表すのに2の補数を用いるのは、計算が容易だからです。まず、コンピュータでは負の数を扱うときに“-”の記号は(コンピュータの内部構造が複雑になるので)使用できません。そのため、最上位ビットを符号として負の数を扱うことにしました。例えば、4ビットで0001(1)の負の数は1001(-1)になります。
ここで、1 + (-1)を計算します。単純に計算すると0001 + 1001 = 1010になってしまいます。これは-2なので、負の数の加算処理は単純に行えなくなってしまい、正しい計算をするにはやはりコンピュータとして複雑なことをしなくてはなりません。
そこで、考え方を変えて補数で負の数を表してみます。-1は1の2の補数として表すと1が0001で2の補数は0と1を入れ替えて+1ですから1111になります。これで1 + (-1)を計算すると、0001 + 1111 = 1 0000(16)になります。
ここで、4ビットで考えているので5ビット目の“1”は無視できるので、答えは0000(0)になります。
また、2の補数を使うメリットは0の対し2の補数を計算すると0になることです。0000(0)の2の補数は0と1を入れ替えて1111に+1で1 0000で5ビット目の1は無視して0000です。
最上位ビットを符号として使用する場合、0として0000(+0)と1000(-0)の2通りが考えられますが、そういったこともなくなりました。

4ビットの2進数で扱える数は以下のようになります。

2進数 10進数
0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 -8
1001 -7
1010 -6
1011 -5
1100 -4
1101 -3
1110 -2
1111 -1
この例でわかるように2の補数を使うと正側と負側で表現できる数の範囲が違います。Nビットで2の補数を使ったときに表現できる数は-2N-1〜(2N-1 - 1)です。

一見、わかりづらい2の補数による負の数の表現ですが、負の数の加算処理が単純になるメリットがあるため、コンピュータで使用されています。


2の補数の注意点

最上位ビットを符号としてしているので、加算や減算でけたあふれ(オーバフロー)が発生するような場合、注意が必要です。
例えば、4ビットで5+3を計算する場合、5は0101、3は0011で、5 + 3 = 0101 + 0011 = 1000になり、1000は上の表にあるように-8ですので、4ビットの2の補数表現でこの計算はできないことになります。

また、C言語だと変数の型として符号のない表現(unsigned)もありますが、符号付き変数と符号なし変数の扱いには注意が必要です。
例えば、8ビットの変数(char)で、符号付き変数(char)の-1と符号なし変数(unsigned char)の255は、2進数で表すとどちらも1111 1111です。
(“char”の範囲は-27(-128)〜27 - 1(127)、“unsigned char”の範囲は0〜28 - 1(255)です。)
不具合(バグ)の発生する例としては、unsignedの変数を使用したときに、負の数と比較するような処理があります。
 void func(void)
 {
  unsigned char a;

  for(a=0; ; a++)
  {
   処理;
   if(a == -1)
    break;
  }
 }
処理系にもよりますが、上の例では変数aが255になると、if文の条件が真になり、for文を抜けてしまう惧れがあります。
逆に“処理”の中で変数aに-1をセットして、for文から抜けようとしても、うまくいかないこともあります。

戻る 一覧へ