Last modified: Thu May 29 16:51:53 JST 2008

伊達 >> 情報工学序説 >> 課題一覧 >> 演習4

コンピュータ内部では,数値はどのように表現されているのだろう?

演習の目的

数値をファイルに書き込み, コンピュータ内部で数値がどのように表現されているか確認する. 表現できない数を入力するとどうなるかも確認する.

知識の確認

はじまり

  1. emacs などのエディタを使い 以下のプログラムを入力する. もしくはダウンロードする.

    プログラム: number001.c

    #include<stdio.h>
    
    int main(void)
    {
    	FILE *fp;	
    	int x[ ] = {1,2,-1,4,5,65535,7,65536,65537,10};
    
    	if ((fp = fopen("test.dat", "w+")) == NULL) {
    	  printf("file open error!!\n");
    	  exit(-1);
    	}
    
    	fwrite(x, sizeof(int), 10, fp);  /* write 10 numbers */
    	fclose(fp);    
    }
    
  2. プログラムの説明: 一次元配列 x[0] x[1] ... に10個の整数を代入し, x[0]=1, x[1]=2, x[2]=-1, ... としている. この10個の数字を test.dat というファイルに書き込んで終了.

    コンパイルして走らせてみる.

    % gcc number001.c
    % ./a.out
    % ls -l
    
    -rw-r--r--    1 date     date           40 May 20 11:41 test.dat
    
    test.dat というファイルができているか確認する. この40 という数字は,ファイルの大きさが 40バイトという意味. 10個の整数(int)型のデータを書き込んだ結果が40バイト. 1個の数字あたり4バイトが費やされていることが分かる.

  3. od というコマンドを使い,ファイルの中身を16進数で表示する. 16進数なので 4bit が一文字 0,1,...,E,F に対応. 01 とか 0a というのは 8 bit. (ff)16 は 10進数の (255)10

    % od -t x1z -A x test.dat
    000000 01 00 00 00 02 00 00 00 ff ff ff ff 04 00 00 00  >................<
    000010 05 00 00 00 ff ff 00 00 07 00 00 00 00 00 01 00  >................<
    000020 01 00 01 00 0a 00 00 00                          >........<
    000028
    
    1 は 01 00 00 00 と表現されており, -1 は ff ff ff ff と(2の補数)表現されており, 10 は 0a 00 00 00 と表現されていることが分かる. 同様に 65535 は ff ff 00 00 と表現されており, 65536 は 00 00 01 00, 65537 は 01 00 01 00 と表現されていることが分かる.

    これを見て,数の並び方がおかしいと感じるかもしれない. 本来 AB CD EF GH という数が GH EF CD AB の順に格納されている (ここでは4ビットの数を A,B,C 等とした). これは使用している CPU に依存して決まっている. インテルの CPU では「リトルエンディアン」と呼ばれる 方式が採用されている. (エンディアン

    なお, 32bit で一つの整数を表現しているが,正の数,負の数をともにまんべんなく 表現しようとすると 32 bit では ± 231 = ± 2,197,483,648 までしか区別できない.

  4. number001.c のファイルの中で, 書き込む数を適当な値に変えて,プログラムを再実行し, どのような値が保存されるか確認してみる.
    int x[] = {123,-299,65535,4,5,6,7,8,9,10};
    
  5. char, int, double など,それぞれの型の数を一つ 記憶するために何バイトを必要とするかは次のような プログラムを走らせれば確認できる. これも CPUに依存している. プログラム: number002.c
    #include<stdio.h>
    
    int main(void)
    {
            printf("sizeof char= %d\n", sizeof(char));
            printf("sizeof short int = %d\n", sizeof(short int));
            printf("sizeof long int = %d\n", sizeof(long int));
            printf("sizeof int = %d\n", sizeof(int));
            printf("sizeof double= %d\n", sizeof(double));
            printf("sizeof long double= %d\n", sizeof(long double));
    }
    
  6. 私のコンピュータの値.
    % gcc number002.c
    % ./a.out
    
    sizeof char= 1
    sizeof short int = 2
    sizeof long int = 4
    sizeof int = 4
    sizeof double= 8
    sizeof long double= 12
    
  7. fwrite という関数の説明

    man コマンドで調べると

    % man fwrite
    
    size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
    ....   
    The function fwrite writes nmemb elements of data, each size bytes long, 
    
    ということなので,
    fwrite(x, sizeof(double), 1, fp);  /* write a number x[0] */
    
    この場合, x という配列に入っている 1個あたり sizeof(double) バイトの データを1個,ファイルに書き込む,ということ.
    fwrite(x, sizeof(double), 2, fp);  /* write a number x[0] */
    
    1を2にすれば x[0], x[1] が書き込まれる.

  8. 小数点がついている数の表現
    左が10進数,右が2進数です.

    (0.5)10 = 0.1
    (0.25)10 = 0.01
    (0.125)10 = 0.001
    (0.75)10 = 0.11
    (0.1)10 = 0.0001100110011 = 0.1100 1100 1100 x 2 -3 = 1.1001 1001 1001 x 2 -4

    プログラム: number003.c

    #include<stdio.h>
    
    int main(void)
    {
            FILE *fp;
            double x[] = {0.0, 1.0, -1.0, 0.5, -0.5, 0.75, -0.75, 0.1};
    
            if ((fp = fopen("test.dat", "w+")) == NULL) {
              printf("file open error!!\n");
              exit(-1);
            }
    
            fwrite(x, sizeof(double), 5, fp);  /* write a number x[0] */
            fclose(fp);
    }
    
    %od -t x1z -A x test.dat
    000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f0 3f  >...............?<
    000010 00 00 00 00 00 00 f0 bf 00 00 00 00 00 00 e0 3f  >...............?<
    000020 00 00 00 00 00 00 e0 bf 00 00 00 00 00 00 e8 3f  >...............?<
    000030 00 00 00 00 00 00 e8 bf 9a 99 99 99 99 99 b9 3f  >...............?<
    000040
    
  9. 教科書 (p.116, 117) によると 浮動少数点 を 64 bit で使用する場合, 符号 1 bit, 指数部 11 bit,仮数部 52 bit をそれぞれ使う. p.116 に書かれている正規表現で (-1)s x (1.f) x 2e-1023 . これを参考に,この実行結果を解釈してみる.

    +1.0 は 00 00 00 00 00 00 f0 3f と表現されている.
    -1.0 は 00 00 00 00 00 00 f0 bf と表現されている.

    f0 3f = 1111 0000 0011 1111
    f0 bf = 1111 0000 1011 1111

    1.0, -1.0 は符号部以外は同じ表現のはず. これで,符号を表現するビットがどれか分かった.

    整数の場合と同じで AB CD EF GH と数があった場合に, GH EF CD AB という順で格納されているようだ. この解釈をすれば, 1.0 の指数部 e の 11 bit は 011 1111 1111 = 1023 で. 仮数部は 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 である. 仮数部 f はすべて 0 である. (1.0)10 = 1.0 x 20 であるので, 仮数部 f が 0 で指数部 e は 1023 になっていた.

    別の数を見てみよう.

    +0.5 は 00 00 00 00 00 00 e0 3f と表現されている.
    +1.0 は 00 00 00 00 00 00 f0 3f と表現されている.

    0.5 =2-1 , 1.0= 20 は仮数部以外は同じ表現のはず.

    e0 3f = 1110 0000 0011 1111
    f0 3f = 1111 0000 0011 1111

    なるほど! では最後に 0.1 の2進数表現はどうなっているか見てみよう.

    (0.1)10 は bf 9a 99 99 99 99 99 b9 3f と表現されている.
    しようがないので全部かきおこす.

    9a 99 99 99 99 99 b9 3f = 1001 1010 1001 1001 1001 1001 1001 1001 1001 1001 1011 1001 0 011 1111

    (0.1)10 = 1.1001 1001 1001 x 2 -4 であるので, 仮数部 f の 52 bit は 1001 1001 1001 1001 1001 ... のはず... 最後だけ 9a になっている.
    指数部 e は e-1023 = -4 つまり e = 1019 が 11 bit で記述され ているはず. (1019)10 = 1023- 4 = 011 1111 1011

    緑の部分が指数部! 赤の部分が符号.

    自分の調べている数の符号を変えると,符号ビットだけ 反転するので,違いを調べれば,符号ビットがどこかは分かります. 同様に自分の調べている数字を 2 倍するとか,1だけ小さい数が どのように表現されているか,もとの数字の場合と 比較すれば,少なくとも,どこが指数部の一部を担当しているか, どこが仮数部の一部を担当しているかは分かります.