2009 年度「計算機基礎論3B」 2009-11-27

§33 乱数

(33.1) 「乱数」が数学でどう定義されるかは、ここでは考えない。 計算機でいう乱数とは、 種(たね)と呼ばれる整数を元に計算を行なって発生させた、 乱数のように見える数列のことである。

(33.2) 関数 rand() は、呼ばれるたびに、 0 以上 RAND_MAX 以下の整数(int 型)に値をとる乱数の項を、 一つずつ返す。 次のプログラム

#include <stdio.h>
#include <stdlib.h> /* rand() */

main() {
    int i;

    for (i = 0; i < 10; i++) {      /* 10 項からなる乱数列を発生 */
        printf("%d\n", rand());
    }
}
は 10 個の整数を出力するが、これらの並びが乱数、というわけである。

(33.3) RAND_MAX はコンパイラごとに違う値を取り得る定数である。 その値は次のプログラムでわかる。

#include <stdio.h>
#include <stdlib.h> /* RAND_MAX */

main() {
    printf("%d\n", RAND_MAX);
}

(33.4) (33.2) のプログラムは、毎回同じ乱数列を出力する。 それでは困るので、現在時刻を乱数の種(たね)にすることを考える。

#include <stdio.h>
#include <stdlib.h> /* rand() */
#include <time.h>   /* time() */

main() {
    int i;

    {   /* この中カッコの中(乱数の種の設定)はわからなくてもよい */
        unsigned seed = (unsigned)time(NULL);   /* 現在時刻を取得して */
        printf("乱数の種は %u です.\n", seed);
        srand(seed);                            /* それを乱数の種に */
    }

    for (i = 0; i < 10; i++) {
        printf("%d\n", rand());
    }
}
このコンパイラでこのプログラムを動かすと、 現在時刻の秒未満は無視されるようである。

(33.5) 種の値を出力させるのは、再現性のためである。 すなわち、 このプログラムが出力した種を次のプログラム (乱数の種の設定の部分以外は省略)に入力すれば、 全く同じ乱数列が得られる。

    {   /* この中カッコの中(乱数の種の設定)はわからなくてもよい */
        unsigned seed;

        printf("乱数の種を入力してください.\n");
        scanf("%u", &seed);
        srand(seed);                            /* それを乱数の種に */
    }

(33.6) rand() % 100 とすると、 0 以上 100 未満の整数に値をとる乱数が得られる。 厳密に言えば 0 の出現率が 99 のそれよりわずかながら高いが、 この授業ではそこまでは気にしないことにしよう。

(33.7) rand() / (1.0 + RAND_MAX) とすると、 区間 [0, 1[ に値をとる乱数になる。 1 でなく 1.0 と書くのは、 加法を int 型でなく double 型の中で行なうためであるが、 この授業ではこのあたりの問題には深入りしないので、 全体で“決まり文句”として理解しておけばよい。

(33.8) それを使って円周率の近似値を求めるプログラム。 x, y は [0, 1[ に値を持つ乱数になる。 これらを x 座標 y 座標に持つ点を平面上にとると、 それは 1×1 の正方形の中のランダムな点となる。 その点と原点との距離が 1 以下になる確率は四分の一円の面積 π/4 に等しいから、 N 回のうち p 回だけその四分の一円にはいったとすると π の近似値として 4*p/N が得られる。 (プログラム内で 4.0 * p / N としているのは、 pNint 型なのでこうしないと割り算の結果が int 型になってしまうためである。 よって printf("... %f です.\n", 4 * p / N); は正しいプログラムでないし、 printf("... %d です.\n", 4 * p / N); では答えは「3」になる。)

#include <stdio.h>
#include <stdlib.h> /* rand() */
#include <time.h>   /* time() */

#define N 10000     /* くり返しの回数。増やせば結果の精度が上がる */

main() {
    int i, p;
    double x, y;

    {   /* この中カッコの中(乱数の種の設定)はわからなくてもよい */
        unsigned seed = (unsigned)time(NULL);   /* 現在時刻を取得して */
        printf("乱数の種は %u です.\n", seed);
        srand(seed);                            /* それを乱数の種に */
    }

    p = 0;
    for (i = 0; i < N; i++) {
        x = rand() / (1.0 + RAND_MAX);  /* 0 <= x < 1 を満たす乱数 */
        y = rand() / (1.0 + RAND_MAX);
        if (x*x + y*y <= 1) {
            p++;
        }
    }
    printf("円周率の近似値は %f です.\n", 4.0 * p / N);
}

(33.9) ユーザにランダムな数を与えて計算問題をさせるプログラムも書ける。 例えばこんな感じ。

33 + 14 = 47
正解です!
44 + 29 = 63
ブー!


岩瀬順一