コンピュータで負の数を表す方法は複数ありますが、整数の計算をする時には、ここで説明する「2の補数」を使うのが便利です。
ここでは簡単のために4ビットで計算するコンピュータを考えます。
内部表現が 0000 になっているところから -1 すると 1111 になります。これを -1 と解釈することにしました。
| 内部表現 | 10進数 | ||||
|---|---|---|---|---|---|
| 正の数とする 時(0〜15) |
負の数も考え る時(-8〜7) |
||||
| 0 | 0 | 0 | 0 | 0 | 0 |
4ビットを素直にそのまま2進数と解釈すると今までどおり 0 から 15 まで表現できます。
このうち 8 から 15 まで、つまり最高位のビットが1の時だけ、2の補数を使った特殊な解釈をして -8 から -1 に割り当てます。この結果 8 から 15 までを表現できなくなりますが、4ビット全体で -8 から 7 まで表現できるようになります。
2の補数というのは、2進法で、ある数 a に足したときに桁が増える数のうち最も小さい数です。特殊な解釈というのを説明しましょう。 8 から 15 までは2進法では1000から1111ですから4桁の数です。たとえば1101に11を加えると10000になって桁が5桁になります。11より小さいと5桁になりません。11は十進法では3ですから3にマイナスをつけて、1101は-3であるとします。
4ビットなら、1111 が -1 ですが、
8ビットなら、11111111 が -1 になります。
| 内部表現 | 10進数 | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| 正の数とする 時(0〜255) |
負の数も考える 時(-128〜127) |
||||||||
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
8ビットを素直にそのまま2進数と解釈すると今までどおり 0 から 255 まで表現できます。
このうち 128 から 255 まで、つまり最高位のビットが1の時だけ、2の補数を使った特殊な解釈をして -128 から -1 に割り当てます。の結果 128 から 255 までを表現できなくなりますが、8ビット全体で -128 から 127 まで表現できるようになります。
2の補数というのは、2進法で、ある数 a に足したときに桁が増える数のうち最も小さい数です。特殊な解釈というのを説明しましょう。 128 から 255 までは2進法では10000000から11111111ですから8桁の数です。たとえば11111100に100を加えると100000000になって桁が9桁になります。100より小さいと9桁になりません。100は十進法では4ですから4にマイナスをつけて、11111100は-4であるとします。
コンピュータにとって負の数を含む計算をするときに2の補数をつかうと便利だというのは、また後で出てきますが、ここではざっくりと、大きい方の数を負の数に割り当てていることを理解してください。
この取決めをちゃんとしないと、1111 が 15 なのか -1 なのかを取り違えてコンピュータが間違った結果を出す危険があるということも大切です。
2進法では負の数を表すのに「2の補数」を使うと言い切っては間違いです。
普通のの2進法では、10進法同様にマイナスの記号を使って悪いことはありません。つまり、
-1, -11, -1010
という方法です。
コンピュータでは2の補数を使っているということです。ただし、コンピュータは負の数を表すのに「2の補数」を使うと言い切るのも違います。整数で表せない数は浮動小数点数という形式を使いますが、この方式では2の補数を使わないからです。
増やしていって途中で負の数に変わるのは納得出来なくても、0から減らして-1,-2,-3となり、それに加えていって0に戻る様子から、わかってもらうのが狙いです。
授業で説明したときは「2の補数として解釈すると負の数になる」かのように書いてしまっていました。今回考えなおして書きなおしましたが、まだ回りくどいと思います。間違った理解に基づいたほうがわかりやすい説明ができるというのは困ったものです。
高校の情報の授業でここまで触れる必要はないかもしれませんが、正しくないことを断言し、暗記させ、試験に出すのはいただけない。歴史のある数学や理科では難しい部分に触れないで説明するノウハウが蓄積されていて、教科書の記述に感心することも多いのですが、歴史の浅い情報科では不正確な記述がまだ残っています。
誤解するかもしれない点をいくつかあげてみます。
・数学で定義された補数に従うと、正の数とその補数を足しても0にはならず、正の数になるのですから、補数は負の数ではありません。
・定義では「足すと桁上がりする数のうち最小の数」ですから、1101に対する2の補数は11ですが、11に対する2の補数は1です。2桁の11に1を足して100で3桁になることでわかります。補数という言葉から相補うような印象を持ちますが、11を0011と書いてこれに対する2の補数をとって1101とするという説明は無理があります。CPUのレジスタにおいてはそうなりますが、コンピュータは01を反転して1を加えているのであって2の補数を求めていると説明すると正しくはないでしょう。
プログラムは4ビットと8ビットを分離できるように独立して作ってあります。
正の整数の表現で使ったプログラムとほとんど同じです。
<script>
var znum4 = 0;
function incnum4(){
if (15>znum4){
znum4 += 1;
}else{
znum4 = 0;
}
disp4();
}
function decnum4(){
if (znum4>0){
znum4 -= 1;
}else{
znum4 = 15;
}
disp4();
}
function clrnum4(){
znum4 = 0;
disp4();
}
function disp4(){
var tgt = document.getElementById("reg4");
var tgttd = tgt.getElementsByTagName("td");
var bstr = "000" + znum4.toString(2);
bstr = bstr.substring(bstr.length-4);
for (var i=0;bstr.length>i;i++){
tgttd[i].firstChild.nodeValue = bstr.substring(i,i+1);
}
tgttd[bstr.length].firstChild.nodeValue = znum4.toString(10);
var zz = znum4;
if (zz>7) zz=zz-16;
tgttd[bstr.length+1].firstChild.nodeValue = zz.toString(10);
}
</script>
<table class="reg"> <tr> <th colspan="4" rowspan="2">内部表現</th> <th colspan="2">10進数</th> </tr> <tr> <th>正の数とする<br>時(0〜15)</th> <th>負の数も考え<br>る時(-8〜7)</th> </tr> <tr id="reg4"> <td>0</td> <td>0</td> <td>0</td> <td>0</td> <td class="num">0</td> <td class="num">0</td> </tr> <tr> <td class="incdec" colspan="6"> <button type="button" onclick="incnum4()">+1</button> <button type="button" onclick="decnum4()">-1</button> <button type="button" onclick="clrnum4()">clear</button> </td> </tr> </table>
table.reg{
border:none;
margin:1%;
}
#reg4, #reg8{
font-size:200%;
text-align :center;
}
#reg4 td, #reg8 td{
padding:2px 6px;
}
td.num{
text-align :right;
}
td.incdec{
padding-top:0.8em;
text-align :left;
border:none;
}
button{
cursor:pointer;
min-width:4em;
}
<script>
var znum8 = 0;
function incnum8(){
if (255>znum8){
znum8 += 1;
}else{
znum8 = 0;
}
disp8();
}
function decnum8(){
if (znum8>0){
znum8 -= 1;
}else{
znum8 = 255;
}
disp8();
}
function clrnum8(){
znum8 = 0;
disp8();
}
function disp8(n){
var tgt = document.getElementById("reg8");
var tgttd = tgt.getElementsByTagName("td");
var bstr = "0000000" + znum8.toString(2);
bstr = bstr.substring(bstr.length-8);
for (var i=0;bstr.length>i;i++){
tgttd[i].firstChild.nodeValue = bstr.substring(i,i+1);
}
tgttd[bstr.length].firstChild.nodeValue = znum8.toString(10);
var zz = znum8;
if (zz>127) zz=zz-256;
tgttd[bstr.length+1].firstChild.nodeValue = zz.toString(10);
}
</script>
<table class="reg"> <tr> <th colspan="8" rowspan="2">内部表現</th> <th colspan="2">10進数</th> </tr> <tr> <th>正の数とする<br>時(0〜255)</th> <th>負の数も考える<br>時(-128〜127)</th> </tr> <tr id="reg8"> <td>0</td> <td>0</td> <td>0</td> <td>0</td> <td>0</td> <td>0</td> <td>0</td> <td>0</td> <td class="num">0</td> <td class="num">0</td> </tr> <tr> <td class="incdec" colspan="10"> <button type="button" onclick="incnum8()">+1</button> <button type="button" onclick="decnum8()">-1</button> <button type="button" onclick="clrnum8()">clear</button> </td> </tr> </table>
cssの部分は、4ビットと共通です。