負の数と浮動小数の表現

  1. 負の整数の計算


    1. 負の数の表現

       負の数を表現するには符号を示す1ビットが必要です。この符号を示すビットを先頭のビットとすると、マイナス3 を8ビットで表現すると次のようになります。
       1000 0011
      しかしながら、この数と +3 である 0000 0011 を加えても0にはならず、
       1000 0110
      となり、これは -6 になります。正の数と負の数を加える場合、負の数を正の数として減算をする必要があります。逆に、正の数から負の数を引くには加算をする必要があります。

    2. 補数と減算

       この面倒な符号の問題を解決し、さらに減算器なしで加算器のみで減算も可能としたのが、負の数を「補数」であらわす方式です。数Aに対する補数 Bは A と同じ桁数で A + B を計算すると 同じ桁数の範囲で A + B = 0 になる数です。したがって、負の数として補数表現を用いると、ある数とその負の数を加えると 0 になります。また、減算は補数回路と加算回路で実行できます。補数回路は減算回路よりずっと簡単な回路です。

    3. 10の補数

       2進数の歩数を紹介する前に、10進数の歩数を紹介します。3桁の10進数の場合、A の補数 B は 999-A+1 です。1見、1000-A に見えますが、1000 の先頭の1は3桁に限定すると、(記憶されないので)無視される数です。A = 311 の場合の補数は (999-311) + 1 = 688 + 1 になります。補数の計算は 999 からの減算のため(借りが発生しないので)簡単に計算できます。
       この補数を用いると、減算は補数の加算で行うことができます。たとえば次のように計算します。
       653 - 311 = 653 + (311の補数) = 653 + (999-311) +1 = 653+688+1 = (1)341+ 1 = (1000) + 342
      となります。3桁の範囲では 1000 は 0 と同じです。補数の計算をすることで、減算を補数計算と加算で実行できます。

      演習1
       311-653 を 653 の補数を用いて計算してください。

    4. 2の補数:負の数の表現

       10進数では負の数を先頭に - をつけて表示します。2進数では符号も 0,1で表示します。8ビット表記の場合、一番左の桁を符号の桁として、
          -5 を 10000101
      と表示してみます。5 と -5 を単順に加算しても、
           00000101 + 10000101 = 10001010
      となり、0 になりません。そこで、2の補数を利用します。同じ桁数 b の範囲で n に加算したら 0 になる数を2の補数(以後単に補数)とよび、 n の負の数とします。n の補数は、n の2進表記の各桁の0と1を反転し 1を加えます。この補数と n を加えると、下 b 桁は 0 になります。
       たとえば、8ビットの範囲で n = 00000101(10進5) の場合、0,1 を反転すると 11111010 となり、これに1を加えると、1111011 となります。これが 5 の補数で、00000101 と加えると 1 0000 0000 となり、8ビットの範囲で考えると0になります。
             0000 0101
           + 1111 1011
            ------------
            10000 0000
       どうように、1111 1011 の補数は  0000 0100 +1 ですから 10進数で 5 になります。負の数として補数を利用することで、符号に関係なくそのまま加えることができます。また、減算 A - B は、A +(Bの補数) となり補数を求めることで、補数と加算で減算ができます。補数を求めることは減算より簡単です。負の数の最上位ビットは1になりますから、最上位ビット(下図 S)を符号ビットと呼ぶます。

      なお、単に0と1を反転した数(最後に1を加えない数)を「1の補数」と呼ぶことがあります。「1の補数」に1を加えると2の補数になります。


       54 - 23 を補数と加算で計算します。54 は 0011 0110 、23 は 0001 0111 です。0001 0111 の補数は 1110 1001 ですから、[54-23]= 0011 0110+(0001 0111の補数) = 0011 0110+1110 1001 = 1 0001 1111
      となります。先頭の1は8bitの範囲では無視でき、0001 1111=[31] となります。
       [23-54] は、0001 0111 + (0011 0110の補数) = 0001 0111 + 1100 1010 = 1110 0001 となり、これは、
       0001 1111=[31] の補数です。

      演習2
      以下を2の補数を用いて8bit2進数で計算してください(結果は2進数)。また、結果を10進数に戻して検算してください。
       6-3
       3-6
       54 - 31
       31 - 54
       
    5. C言語(Windows)の整数表現

       下のプログラムは、C言語で整数の正負の値を10進数と16進数で表示した例です。負の数は2の補数で表現されていますね。for 文では、整数numの4バイトの値を先頭番地(p)から1バイトづつ順に表示しています。
      //補数と整数の内部表現
      #include "stdafx.h"
      int _tmain(int argc, _TCHAR* argv[])
      {
              int num=20;
              int i;
              unsigned char *p;
              printf("10進数:%d,%d\n",num,-num);
              printf("16進数:%08x,%x\n",num,-num);
      
              //先頭番地から順に表示する
              printf("16進数:");
              p = (unsigned char *)#//numの先頭番地をpに
              for (i=0; i<sizeof(num); i++) {
                printf("%02x ", *(p));
                p++;//次の番地
              }
             printf("\n");
      
            return 0;
      }
      
      //結果
      //10進数:20,-20
      //16進数:00000014,ffffffec
      //16進数:14 00 00 00
      int は4バイトで構成されますが、下位のバイトから順にメモリに配置されます。この記憶形式をリトルエンディアンといいます。上位バイトを先頭に配置するビッグエンディアン方式を採用しているコンピュータもあります。

      演習3
       表示されている16進数の値が正しいか?確認しなさい。

    6. 2をかけると負になる

      以下は、2のべき乗を計算するプログラムです。最後の結果が 負の数になっています。なぜでしょう?。
      //2のべき乗の計算
      #include "stdafx.h"
      int _tmain(int argc, _TCHAR* argv[])
      {
          int p=1,i;
          printf("べき乗    10進数    16進数\n");
              for(i=1;i<32;i++){
                      p = p * 2;
                      printf("%2d: %12d ,%8x\n",i,p,p);
              }
              return 0;
      }
      
      結果
      べき乗    10進数    16進数
       1:            2 ,       2
       2:            4 ,       4
       3:            8 ,       8
      
       8:          256 ,     100
       9:          512 ,     200
      10:         1024 ,     400
      
      28:    268435456 ,10000000
      29:    536870912 ,20000000
      30:   1073741824 ,40000000
      31:  -2147483648 ,80000000

  2. 浮動小数の表現(参考)


    1. 浮動小数点表現

       小数点の位置を示すための、小数を
         (-1)s * m * 2E
      の形式で表現し、s、m と e を次のように表記します。この表記法は IEEE 745 で規格化されたもので、比較的よく利用されています。s は符号ビット、E は指数、m を仮数、と呼びます。E は)補数表現でなく、単精度少数の場合 E に127 を加えた e で表現します(これをオフセット表現といいます)。



       e の数で小数点の位置が変動(浮動)するため、浮動小数点(floating point )と呼ばれます。仮数 m は 1.m を意味し、小数点の位置が最上位ビットの後ろになるよう正規化します。
      単精度少数の場合の例をあげます。m は絶対値表現で、符号を s で表現します。
       0 1000 0111 011 0110 1010 0000 0000 0000
       s=0 e=10000111 m=011 0110 1010 0000 0000 0000
      では、e = 10000111 となりこれは 01111111 より 8 だけ大きな数ですから、10進数で E= 8 になります。s=0 ですから、この数は
       1.011 0110 1010 * 28 = 101101101.01 = 365.25
      となります。28 は小数点を右に8桁移動することに注意してください。この数は、16進では
       0100 0011 1011 0110 1010 0000 0000 0000 =  0x43 B6 A0 00
      になります。次のプログラムで、確認ができます。少数も先頭バイトが最後に配置されます。

  3. まとめ、デモ


    1. まとめ

      補数は加えると0になる数である
      減算は補数計算と加算で計算できる
      言語Cで整数の大きな数を計算すると、負の数になることがある。
      少数は浮動少数点形式で計算する

    2. デモ

      C言語の負の数(プログラム)
      C言語の浮動少数表現(プログラム)

  4. 問題、課題


    1. 総合演習

      1 :以下の3桁の10進数の減算を、補数と加算で計算しなさい
       213-30
       330-213

      2 :以下を8bitの2の補数を用いて計算しなさい。また、結果を10進数に戻して確認しなさい。
       54 - 31
       31 - 54

    2. アンケート

      ■0101+011 の値は
        1:0111 2:1001  3:0110  4:1000

      ■補数表現は理解できましたか
       1できた 2:たぶん 3:よくわからん

      ■2の補数による減算はできましたか
       1できた 2:できない

      ■8ビットの2の補数で表現できる一番小さな数はどれか。
       1:-9999 2:-127 3:-128

      ■ 8ビットで表現するとき、6の2の補数はどれか
       1:1000 0110 2:11111 1001 3:1111 1010 4:どれでもない

    3. レポート課題

      1:総合演習の解答をかきなさい。

      2:16bitで負の数を2の補数で表現する場合、表現できる最大値と最小値を求めなさい。

      3:C言語で1に2をかけていき(1*2、1*2*2、1*2*2*2)、32回以上かけると結果が負の数になる。
       これを確認し、その理由を説明しなさい。