2000 年度「計算機基礎論3B」 乱数で遊ぼう

「乱数」とは、 サイコロを繰り返し振って出た数を並べたような無規則な数の列のことです。 C言語には rand() という、乱数を発生させる関数が備わっています。 rand() は int 型を返す関数で、 0 から RAND_MAX までの整数をランダムに返します。 引数はありません。

※ 定数 RAND_MAX をプログラムの中で使うには stdlib.h を #include する必要がある。 しかし、rand() が stdlib.h を必要とするので、 よほどアマノジャクなプログラムでなければ RAND_MAX だけのために stdlib.h を #include するようなことはしないだろう。

rand() は種(たね)から計算を繰り返して数を発生させるので、 厳密な意味では乱数とは言えず、“疑似乱数”というべきなのかも知れません。 しかも、種はいつも同じなので、 プログラムを動かすたびに毎回同じ数列になってしまいます。 そこで、 下のプログラム例の 「現在時刻で乱数の種をセット」 とある行のようにするのが普通です。 その行をコメントアウト (/* ... */ で囲んでコメントにし、 実行されないようにしてしまうこと) したりして、実験してみてください。

※ コメントを入れ子にすることはできないので、 下の例では行頭に「/*」をつけるだけでよい。 つまり、

/*    srand((unsigned int) time(NULL));   /* 現在時刻で乱数の種をセット */ */
はダメ、
/*    srand((unsigned int) time(NULL));   /* 現在時刻で乱数の種をセット */
は OK.

なお、その行にはまだ説明していないことが使われていますが、 今は説明しないことにします。

== random.c ===================================================================
#include <stdio.h>
#include <stdlib.h>     /* rand(), etc */
#include <time.h>       /* time() */

main() {
    int i;

    printf("RAND_MAX は %d です.\n", RAND_MAX);

    srand((unsigned int)time(NULL));    /* 現在時刻で乱数の種をセット */
    for (i=0; i<20; i++) {              /* 20 個の乱数を発生させ印字 */
        printf("%d ", rand());
    }
    printf("\n");
                                        /* 以下は失敗。なぜだ? */
    for (i=0; i<20; i++) {
        srand((unsigned int) time(NULL));
        printf("%d ", rand());
    }
    printf("\n");
}
===============================================================================

次のプログラムは、 0 以上 1 以下の乱数を二つ続けて発生させ、 それを平面上の点の座標とみなして、 原点を中心とする半径 1 の四分の一円内にはいる確率から円周率の近似値を出しています。

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

#define N 30000

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

    srand((unsigned int)time(NULL));    /* 現在時刻で乱数の種をセット */
    count = 0;
    for (i=0; i<N; i++) {
        x = (double)rand()/RAND_MAX;    /* [0,1] の乱数に変換 */
        y = (double)rand()/RAND_MAX;
        if (x*x + y*y <= 1) {           /* 四分の一円内にあればカウント */
            count++;
        }
    }
    printf("円周率は約 %f です.\n", (double)4*count/N);
}
===============================================================================

1 から 6 までの乱数を発生させるには 「rand() % 6 + 1」とすればよいでしょう。 これでは 1 から 6 までが同じ確率で出ない場合もありますが、 誤差は小さいのでゲームなどに使う分には構わないでしょう。

みなさんが小学生を教えていると仮定して、 乱数を利用して 「3桁×3桁」の掛け算の問題を出すプログラムを書いてみませんか?  (課題ではありません。)

     325
  × 283
 --------
のように出題し、次にリターンを押すと
     325
  × 283
 --------
     975
   2600
   650
 --------
   91975
のように筆算の結果が出力される、というものです。

※ ……と思ったのだが、 いままで出てきたことだけでは 「リターンを押すと」が書けないかも。 だったら答えのほうだけ出力するプログラムでもよい。

※ トランプゲームを作る際には、 カードは int 型の変数一つで表わすと簡単だ。 値は 0 から 51 までとし、 4 で割った商に一を加えたものをカードの数(Aなら 1 とか)、 4 で割った余りを種類(0 がスペードとか、適当に決める) とすればよい。 これはメモリが足りなかった時代の古典的なテクニックらしい。 (まだ教えてないけど、char 型にすればさらにメモリが節約できる。)

※ スペードやハートの記号は文字としてはないので、 「●」「▲」「▽」「◇」 で代用するなどしてほしい。

※ ポーカーゲームの 「ワンペア」「ツーペア」「スリーカード」「フルハウス」「フォアカード」 の判定は、 「5枚のカードのうち、同じ数をもつ2枚の組」 の数で判定できる。 これも昔からあるテクニックらしい。

※ 一組のトランプを“切る”方法については、 各自で考えて実験してみてほしい。


岩瀬順一