サイトマップ / C言語講座出入り口総目次目次:時刻と時間>起動後の経過時間

青い直線

コンピュータを起動させてからの経過時間

青い直線

[バッファリング有り無し]←このソース→[コンピュータの時計]

/* 今日は、0 から 9 の範囲の整数の乱数を発生させる関数を作り、発生頻度を調べます。いつも同じ系列で乱数が発生するのは面白くないし、実用上問題があります。 */

/* 乱数のシード */

/* その度に違った系列で乱数を発生させるには、シード ( 種 ) を使います。シードをしまう変数を、seed とします。seed を設定する関数と、乱数を発生させる関数を作ります。

seed の範囲を決めるため、先に、乱数を発生させる関数を作ります。でたらめな数に対して何かの計算を繰り返した時、その値が増加を続けても、減少を続けても、安定して乱数を発生できません。最悪の場合、コンピュータで扱うことができる範囲を越えて、オーバーフローのエラーが出てしまいます。それを防ぐため、最後の計算は割り算の余りを求めることにします。

どういう数で割ったら良いでしょうか。unsigned int が2バイトで、unsigned long が4バイトならば、unsigned int で表すことができる値は、65536 ( ゼロを含む ) で、unsigned long で表すことができる値は、4294967296 ( ゼロを含む ) です。

ふたつの範囲にはどのような関係があるでしょう。4バイトで表せる数は、2バイトで表せる数の2乗です。そこで、seed の初期値を、unsigned int の最大値 ( 65535 ) より小さい数とします。計算は、seed の初期値より大きくて、かつ、その2乗より小さい数になるようにします。

計算結果をunsigned int の最大値で割った余りを求めます。下に示したものが、0から9の範囲の乱数を返す関数です。

    #define MUL 12345
    #define INC 54321
    #define MOD 65535        // unsigned int の最大値

    int GetRand(void)
    {
        seed = (MUL * seed + INC) % MOD;
        return (seed % 10);
    }

(MUL * seed + INC) の計算結果は、seed が最大値 ( 65534 )をとっても、unsigned long の最大値 ( 4294967295 ) を越えません。MUL と INC は、この条件を満たせば良いわけです。

次に seed を設定する関数を作ります。seed をメモリのどこへ置くかということを検討します。seed は GetRand( ) から、度々読み込み書き込みが行われます。また、GetRand( ) から抜けても、その値を保持していなければなりません。そこで、静的変数とします。seed を設定する関数からも、書き込みができないといけないので、ふたつの関数より前で宣言します。seed の初期値には制約があります。unsigned int の最大値 ( 65535 ) を越えてはいけません。 */

/* コンピュータの時計 */

/* コンピュータはふたつの時計を持っています。ある時 ( 例えばグリニッジ標準時 1970 年 1 月 1 日 0 時 0 分 0 秒 ) からの経過時間を秒の単位で持っている時計と、コンピュータの電源を入れてからの経過時間を、tick 単位で保持している時計があります。

    注:システムによっては電源を入れてからの経過時間でないものもあります。

tick 単位が何秒になっているかはシステムによって違います。どちらを使っても良いのですが、今回は後者を使います。時間を取り出すには、次に示す標準ライブラリ関数を使います。

    #include <time.h>
    clock_t clock(void);

    例:ticks = clock(  );
	
    戻り値    コンピュータの電源を入れてからの tick 単位の経過時間

clock_t というデータ型は、time.h の中で、

    typedef long clock_t;

などと、宣言されています。clock_t の型はシステムによって、違う可能性があります。long で表すことができる範囲は、多くのシステムで、-2147483648 から +2147483647 です。

clock( ) でコンピュータの電源を入れてからの経過時間を、tick 単位で取り出します。tick 単位が何秒になっているかはシステムによって違います。time.h の中に以下の記述があれば、1 tick は 1 秒です。

    #define CLOCKS_PER_SEC 1

1 tick が何秒であっても、clock( ) の戻り値を、CLOCKS_PER_SEC で割れば秒単位になります。 その値を unsigned int の最大値で割った余りを求め、seed の値とします。seed を設定する関数はこうなります。

    static unsigned int seed;

    void SetSeed(void)
    {
        seed = ((unsigned int)(clock(  ) / CLOCKS_PER_SEC) % MOD);
    } */

/* ソースプログラムの説明 */

/* 今回のソースプログラムでは、GetRand( ) を 100000 回呼び出して、乱数の発生頻度を count[ ] に保存して、表示します。その際、大まかな頻度を * の棒グラフで同時に表示します。 */

/* ここからソースプログラム */

#include <stdio.h>
#include <time.h>      /* clock(  ) で必要 */
#include <string.h>    /* strcat(  ) で必要 */

#define MAX_CAL 100000        /* 乱数を発生させる回数 */
#define MAX_STRLEN 60         /* **** ( 棒グラフ ) の文字列の長さ */

void SetSeed(void);
int GetRand(void);
void main(void);

#define MUL 12345
#define INC 54321
#define MOD 65535             /* unsigned int の最大値 */

static unsigned int seed;     /* 乱数のシード */

  /* 乱数のシードを設定 */
void SetSeed(void)
{
    seed = ((unsigned int)(clock(  ) / CLOCKS_PER_SEC) % MOD);
}

  /* 0 から 9 の乱数を返す */
int GetRand(void)
{        /* MUL * seed + INC は unsigned long の最大値を超えない */
         /* seed は unsigned int の最大値を超えない */
    seed = (MUL * seed + INC) % MOD;	
    return (seed % 10);
}

void main(void)
{
    char format[80] ;                  /* 書式文字列で使用 */
    char str[MAX_STRLEN + 1] = "";     /* 棒グラフで使用 */

    unsigned long count[10];           /* 乱数の頻度を保存 */
    int i;
    unsigned long n = MAX_CAL;         /* MAX_CAL 回乱数を発生させる */

    for (i = 0; i < 10; i++)           /* count[  ] を初期化 */
        count[i] = 0UL;
	
    for (i = 0; i < MAX_STRLEN; i++)	/* * が連続した文字列を作る */
        strcat(str, "*");
	
    SetSeed(  );               /* 乱数のシードを設定 */
	 
    while (n--)                /* MAX_CAL 回乱数を発生させ */
        count[GetRand(  )]++;  /* その頻度をcount[  ] に保存 */

    for (i = 0; i < 10; i++) {  /* 下の説明を参照 */
        sprintf(format, "%%.%lus", count[i] / (MAX_CAL / 400));

        printf("\n%d: %6.0lu\t", i, count[i]);    /* 頻度を表示 */
        printf(format, str);                      /* 棒グラフを表示 */
    }
    printf("\n");
}

/* ここまでソースプログラム */

/* for ( ) ループの中にある下記のコードはわかり難いので、解説します。

    sprintf(format, "%%.%lus", count[i] / (MAX_CAL / 400));

sprintf( ) は標準ライブラリ関数です。

    #include <stdio.h>
    int sprintf(char *s, const char *format, ...);

    例:sprintf(s, format, ...);

    実行結果        戻り値
    成功            出力した文字数
    失敗            EOF

printf( ) に似た関数ですが、データー引数( ... )を、書式文字列( format )に従って、文字列( s )に書き込みます。今回のコードの書式文字列を見てみましょう。

    "%%.%lus"

最初のふたつの%によって、文字列に%がひとつ書き込まれます。次に、良く見るとピリオッドがあります。%に続いて、ピリオッドが書き込まれます。

書式指定子%luで、引数が unsigned long 型に変換され、文字列に書き込まれます。引数を下記に示します。

    count[i] / (MAX_CAL / 400)

乱数がうまく生成されれば、count[i] は、MAX_CAL の約 1/10 ( 10000 ) になります。それを、MAX_CAL の 1/400 で割るのでこの引数は、40 前後の数になります。従って、ピリオッドに続いて40 前後の値が文字列に書き込まれます。次いで、s が書き込まれ、最後にNULL文字 ( '\0' ) を追加します 。引数を40とすると、"%.40s" になります。これは書式文字列です。最後の printf( ) で使用されます。

"%40s" なら、フィールド幅が 40 の文字列ですが、ピリオッドに続いて 40 なので、精度が 40 の文字列を表示します。棒グラフの長さが 40 ということです。 */

[バッファリング有り無し]←このソース→[コンピュータの時計]

青い直線

/* (C) 2000- YFプロ. All Rights Reserved. */    提供:C言語講座−それ自体コンパイルできる教材を使った講座です−

青い直線

サイトマップ / C言語講座出入り口総目次目次:時刻と時間>起動後の経過時間