コンピュータ内部の表現では,非負の整数は通常の2進数で表現される。また,使用するビット長は,8ビット,32ビットなどと固定される。
例えば 25(10) という値は,8ビットで表現する場合 00011001(2) となる。よって,
8ビット使用して表現できる整数は,00000000(2) ~ 11111111(2) ,つまり 0 ~ 28 -1
である。同様に
16ビット使用して表現できる整数は,0 ~ 216 -1
32ビット使用して表現できる整数は,0 ~ 232 -1
である。
コンピュータ内部では,2つの状態 0 と 1 しか使えないので,我々が通常使用する - (マイナス)記号を使うことはできない。つまり,負の数も 0, 1 を使って表現するしかない。
多くの場合,負の数の表現には2の補数を使用する。2の補数とは,正の整数 n に対して,以下のように -n を定義するものである。ここで,数値を表現するビット数は,16や32のように固定して考える。
整数は 8 ビットで表現するものとする。
n = 18(10) とする。(8ビットの場合だと,最初のビットが 0 となる値の範囲は 1(2) ≦ n ≦01111111(2) ,つまり 1 ≦ n ≦ 127(=27-1))
n = 00010010(2)
各ビットを反転して,11101101
これに 1 を加えて,11101110
これが,-18(10) の内部表現である。
n = 0 についても同様の変形をしてみる。
各ビットを反転して,11111111
これに 1 を加えて,100000000
8ビットであるので,最上位のビット1は保持されない。従って結果は,00000000 となり,0 = -0 が成り立つ。
以上から,次のような関係ができる。
元の数 (10進数) |
元の数 (2進数) |
2の補数 (負の数) |
---|---|---|
0 | 00000000 | 00000000 |
1 | 00000001 | 11111111 |
2 | 00000010 | 11111110 |
3 | 00000011 | 11111101 |
4 | 00000100 | 11111100 |
: : |
: : |
|
126 | 01111110 | 10000010 |
127 | 01111111 | 10000001 |
128 | 10000000 | 10000000 |
この表の最下段の 10000000 は元の数と2の補数が同じ表現になっているので,+128 か -128 かどちらかに定めないといけない。負の数の最上位のビットはすべて 1 であるから,負の数とした方が都合がよく,-128 と定める。
10000000(2) = -27 = -128
同様に
16ビット : 1000000000000000(2) = -215 = -32768
32ビット : 1000000000000000000000(2) = -231
と定義する。
この定義から,正の数は最上位のビットが 0,負の数は最上位のビットが 1 となる。
以上のことから,負の数に 2 の補数を使うとき,8ビットの符号付きの整数では -128 ~ 127 の範囲の値が使用できる。
同様に,16 ビットの符号付きの整数では - 215 ~ 215 -1 の範囲の値が,32 ビットの符号付きの整数では - 231 ~ 231 -1 の範囲の値が使用できる。
いずれも負の数の個数が 1 つ多くなる。
負の数に 2 の補数を使う理由は,コンピュータ内部では有効なビット数が固定されていることで,負の数の加算に特別な仕組みを用意しなくても済むからである。
例えば
0000 0001 + 1111 1111 = 1 0000 0000 (*)
となるが,有効ビット数が 8 の場合,右辺の9桁目のビットつまり左端のビットは保持されない。よって 1111 1111 と 0000 0001 の和は 0000 0000,つまり 0 となる。このことから,0000 0001 の負の数として 1111 1111 を採用すれば,負の数を含む和の計算が(従って差の計算も)正の数の和の計算だけでできるのである。
以下,2の補数が負の数として使われる理由を,一般的な場合に考えてみる。
ある正の数 A に対して
A + B = 1 0000 0000 (**)
となる数 B を考える。-1 の場合のように最上位のビットは消えて無くなるので,式(**)は
A + B = 0
という式と同じである。つまり,B が A の負の数の定義を満たしていることが分る。
次に,この B をどのように作成すれば良いかを考える。上の式(*)から,
1 0000 0000 - 0000 0001= 1111 1111
である。よって(**)から
A + (B - 1) = (A + B) - 1 = 1 0000 0000 - 1 = 1111 1111
である。この式から,B-1 は A の各ビットを反転したものであることがわかる。よって B は, A の各ビットを反転したものに 1 を加えたものになる。
例1のように,使用するビット数は8ビットとし,n = 18 とする。例1の計算から,
n =00010010
-n =11101110
である。これらを加えると
00010010 + 11101110 = 100000000
従って,9ビット目を捨てた8ビットの表現では,和が 0 となることが分かる。
2の補数を使うと,正の数と負の数の和(つまり引き算)や負の数度同士の和も,正の2進数の整数の和で計算できる。
21 から 18 を引く計算は 21 と -18 の和を計算する。
21(10) = 00010101(2)
-18(10) = 11101110(2)
21(10) - 18(10) = 00010101(2) + 11101110(2) = 100000011(2) = 00000011(2) = 3(10)
最後から2つ目の等号は,有効桁数を越えた 9 ビット目を消去している。
18 から 21 を引く計算は 18 と -21 の和を計算する。
18(10) = 00010010(2)
-21(10) = 11101011(2)
18(10) - 21(10) = 00010010(2) + 11101011(2) = 11111101(2)
一方 3 の補数は,上の表より,11111101(2)
よって,18 - 21 = -3 が成り立つ。
これらの計算結果から,一般に引き算も2進数の和で計算できることが分る。 つまり加減共に同一の回路で計算できることになり,
回路が単純化でき処理が速くなる
という利点が生まれる。そのため,2の補数を負の表現として採用しているシステムが多い。
数値演算や入力などの結果、扱える数値の最大値を超えることをオーバーフローという。プログラミングなどでは,オーバーフローをチェックせず,計算したままの結果を返すことがある。例えば 8 ビットの符号付の整数の場合,127 + 1 はオーバーフローとなる。よってその事を示唆するメッセージが表示されるのを期待してしまう。しかし実際にはエラーメッセージは表示されずに,-128 が表示されることがある。上の式の計算結果は
0111111 + 00000001 = 10000000
となり,右辺は -128 の2の補数表現なので,-128 という値を返すのである。