1999 年度「計算機基礎論3B」 1999-12-17

はじめに

掲示でもおしらせしましたが、 前回の「課題3」はオプションとします。 (参考:掲示の写し

年が明けたら TeX をやります

前回の連絡をよく読んで、 忘れ物をしないように。

数値型データのメモリへの格納法について

コンピュータが扱える最も基本的なデータは 「0 か 1 か」です。 0, 1 の二つの状態のうちのどちらか一方だけをとる変数を 「ビット (bit)」といいます。 まずほとんどのコンピュータでは 8 ビットをひとまとめにし、 「バイト (byte)」と呼びます。 int 型や double 型は数バイトを寄せ集めたものに格納しますが、 それらの原理をちょっとだけでも見ておくと、 コンピュータと数学の違いがわかると思います。

プログラム例は ~iwase/prog に置いてありますのでコピーして利用してください。 今回のプログラム例には、 この授業でとりあげないことがらが使われています。 また、 もしかしたらほかの WS やコンパイラではうまく動かないかもしれません。 データの格納の方式はコンピュータに依存するので、 出力結果もコンピュータに依存します。

※ 一応、実習用 WS(32 ビット)と自宅の 98(16 ビット)で確認済み。

== intbit.c ==========================================================
#include <stdio.h>
#include <limits.h>     /* CHAR_BIT */

void intbit (int x);

main() {
    int i;

    scanf("%d", &i);
    intbit(i);
    printf("\n");
}

void intbit(int x) {
    int i;
    char* p = (char*)&x;
    unsigned mask;

    for (i=0; i<sizeof(int); i++) {
        putchar(' ');
        for (mask = 1 << (CHAR_BIT - 1); mask != 0; mask >>= 1) {
            putchar(*(p+i) & mask ? '1' : '0');
        }
    }
}
======================================================================

void intbit(int x) は、 int 型変数 x のビットパターンを印字する関数です。

※ intbit というのは適当につけた名前。 「ビットパターン」とはビットが並んだパターン。 (説明になってないか!?)

「void」は「値を返さない」ということを意味しています。 動かすとこんな感じになります。

ws47{cf7175}159% a.out
1047
 00000000 00000000 00000100 00010111
ws47{cf7175}160% a.out
-1
 11111111 11111111 11111111 11111111
ws47{cf7175}161%
この WS では int 型は 32 ビットということもわかります。 1047 は二進で 10000010111 と表現されています。 mod 2^32 なので -1 は一見したところ非常に大きな数になります。

== revintbit.c =======================================================
#include <stdio.h>

char buf[1024+1];

int revintbit(char *buf);

main() {
    gets(buf);
    printf("%d\n", revintbit(buf));
}

int revintbit(char *buf) {
    int ret = 0;

    while (*buf) {
        ret <<= 1;
        if (*buf == ' ') {
            buf++;
        }
        if (*buf == '1') {
            ret++;
        }
        buf++;
    }
    return ret;
}
======================================================================

int revintbit(char *buf) は、 配列 p に格納された 0 と 1 だけからなる文字列をビットパターンだと思って int に変換します。 間にスペースがはいっていても構いませんので、 上の intbit.c(から作った実行ファイル) が出力した結果をリダイレクト (文字列の回を参照)で読ませても OK です。

※ スペースが二つ以上つながっているとだめ。 結構いい加減なプログラム。:-)

== doublebit.c =======================================================
#include <stdio.h>
#include <limits.h>     /* CHAR_BIT */

void doublebit(double x);

main() {
    double i;

    scanf("%lf", &i);
    doublebit(i);
    printf("\n");
}

void doublebit(double x) {
    int i;
    char* p = (char*)&x;
    unsigned mask;

    for (i=0; i<sizeof(double); i++) {
        putchar(' ');
        for (mask = 1 << (CHAR_BIT - 1); mask != 0; mask >>= 1) {
            putchar(*(p+i) & mask ? '1' : '0');
        }
    }
}
======================================================================

void doublebit(int x) は、 double 型変数 x のビットパターンを印字する関数です。

実はこの WS の double 型は 64 ビットであると前もって調べておきましたので、 「岩波情報科学辞典」の「IEEE 規格」の項目から 64 ビットの浮動小数点数の表現方法を引用します。

※ 「浮動小数点数」というのは 6.0221 × 10^23 のように表現された数のことだが、 コンピュータの中は二進なので「× 2 のなんとか乗」となる。

+-+-----+--------------+
|s|  e  |       f      |
+-+-----+--------------+
 1   11         52
e=2047, f≠0非数(Nan)
e=2047, f=0(-1)^s×∞
0 < e < 2047(-1)^s×2^{e-1023}×(1.f)
e=0, f≠0(-1)^s×2^{-1022}×(0.f)
e=0, f=0(-1)^s×0

64 ビットを上の図のように 1 ビット、11 ビット、52 ビットに分けます。

表の三段目が普通の数の場合です。 指数部 e は 11 ビットあるので 0 から 2047 まで表現できますが、 両端の 0 と 2047 は例外とし、1 から 2046 までとします。 それを -1022 から 1023 と見立てるため、 「2 の e-1023 乗」と約束してあるのです。 (1.f) というのは、f の部分に並んでいる 0 と 1 からなる文字列を 「1.」の後ろにくっつけたもの、という意味です。

五段目は 0 です。 0 にも符号 s がついているのは、 2^{-1000} を二乗した場合は正の 0 と考え、 -2^{-1000} に 2^{-1000} を掛けたものは負の 0 と考えるためかと思われます。

四段目は e = 0 のケースを絶対値 「2 の -1022 乗」以下の小さな数を表わすために使ったものです。

二段目は無限大、 一段目は非数です。 非数というのは負の数の平方根などを求めたときに返るのではないかと思います。

どうやらこの WS もこの説明のようになっているようですが、 あまりきちんと確かめてはいません。 また、非数などの場合を確かめるには main を変更しなければならないでしょう。 興味のある人はやってみてください。

== revdoublebit.c ====================================================
#include <stdio.h>
#include <limits.h>

char buf[1024+1];

double revdoublebit(char *buf);

main() {
    gets(buf);
    printf("%f\n", revdoublebit(buf));
}

double revdoublebit(char *buf) {
    int i, mask;
    double ret = 0;
    char* p = (char*) &ret;

    for (i = 0; i < sizeof(double); i++) {
        for (mask = 1 << (CHAR_BIT - 1); mask != 0; mask >>= 1) {
            if (*buf == '\0') {
                goto done;
            } else if (*buf == ' ') {
                buf++;
            }
            if (*buf == '1') {
                p[i] |= mask;
            }
            buf++;
        }
    }
done:
    return ret;
}
======================================================================

double revdoublebit(char *buf) は int revintbit(char *buf) の double 版です。

さて、 次のプログラムは、a が 0 から始まって 0.1 ずつ増えてゆくので 10 回で 1.0 に達し、 ループを抜けるように思えるかもしれませんが、 そうはならず、(たぶん)無限ループに陥ります。 それは、0.1 がコンピュータの中では循環小数となり、 10 回足しても 1.0 ちょうどにはならないからです。 上で紹介した関数と組み合わせると、 そのあたりがよくわかるかもしれません。

== sippai.c ==========================================================
#include <stdio.h>

main() {
    double a;

    for (a=0; a!=1.0; a+=0.1) {
        printf("%f\n", a);
    }
} 
======================================================================

※ 無限ループに陥ったプログラムを止めるには Ctrl+C を押す!


岩瀬順一 <iwase@kappa.s.kanazawa-u.ac.jp>