🖥️

【C言語超入門】 第29回 乱数

2024/12/25に公開

https://youtu.be/Be9UdOKmtdY

四国めたん
\textcolor{pink}{四国めたん: }教師役ですわ

ずんだもん
\textcolor{lime}{ずんだもん: }生徒役なのだ

\footnotesize \textcolor{pink}{四国めたん:} 皆さん、こんにちは。四国めたんです

\footnotesize \textcolor{lime}{ずんだもん:} ずんだもんなのだ。こんにちはなのだ

\footnotesize \textcolor{pink}{四国めたん:} 今回もC言語のお勉強をしていきましょう

\footnotesize \textcolor{lime}{ずんだもん:} レッツゴーなのだ

\footnotesize \textcolor{pink}{四国めたん:} 今回は、少し趣向をかえて、 乱数 の生成についてのお話しをしますわ

\footnotesize \textcolor{lime}{ずんだもん:} 乱数 はゲームを作成するのに、とても役立つのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、ゲーム以外にも、暗号やID生成など、様々な用途で 乱数 が利用されていますわ

\footnotesize \textcolor{lime}{ずんだもん:} ところで 乱数 の正確な定義が良く判らないのだ

\footnotesize \textcolor{pink}{四国めたん:} 乱数 の定義については、この解説の範囲を逸脱するので、知りたい方はネットなどで確認してくださいね

\footnotesize \textcolor{lime}{ずんだもん:} わかった、確認しておくのだ

\footnotesize \textcolor{pink}{四国めたん:} ところで、 乱数 の生成は、コンピューターと非常に相性が良くないことはご存知ですか?

\footnotesize \textcolor{lime}{ずんだもん:} えぇ~、そうなのか?知らなかったのだ

\footnotesize \textcolor{pink}{四国めたん:} コンピューターの特徴の一つとして、一連の処理を正確に、そして順序良く熟していくことが挙げられますわ

\footnotesize \textcolor{lime}{ずんだもん:} たしかに...

\footnotesize \textcolor{pink}{四国めたん:} そこに、例えば 乱数 のような不確定な要素は存在しませんわ

\footnotesize \textcolor{lime}{ずんだもん:} そうなのか?

\footnotesize \textcolor{pink}{四国めたん:} はい、ゲームなどの特殊な用途を除けば、毎回、結果が異なる、もしくは意図した動作をしないようなコンピュータープログラムは失敗作でしかありませんわ

\footnotesize \textcolor{lime}{ずんだもん:} いわれてみればそうなのだ

擬似乱数について

\footnotesize \textcolor{lime}{ずんだもん:} じゃあ、 乱数 の生成が不得意なコンピューターで、どのようにして 乱数 を生成するのだ?

\footnotesize \textcolor{pink}{四国めたん:} 実は、世の中には凄い方々がいて、規則に沿って数値を与えると、 乱数 らしき 答えを得ることができる、数学的な方法を考えてくれていますわ

\footnotesize \textcolor{lime}{ずんだもん:} お~、すごいのだ

\footnotesize \textcolor{pink}{四国めたん:} これらの方法で得られる 乱数 のことを 擬似乱数 と呼びますわ

\footnotesize \textcolor{lime}{ずんだもん:} 方法は1つだけなのか?

\footnotesize \textcolor{pink}{四国めたん:} 擬似乱数 の生成方法は、一般に利用されるような簡単なものから、暗号に使用されるような、 これホントの乱数なのでは? と疑いたくなるような方式まで存在しますわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} では、なぜ 擬似乱数 と呼ばれるかというと、人には判らないレベルではあっても、微妙な偏りがあったり、繰り返しがあったりするからですわ

\footnotesize \textcolor{lime}{ずんだもん:} 本当の 乱数 には、かなわないのだ

C標準関数の乱数を使いましょう

\footnotesize \textcolor{pink}{四国めたん:} まぁ、ゲーム等に使うような乱数であれば、C標準関数のrand関数を使えば、実用上、問題ないと思いますわ

\footnotesize \textcolor{lime}{ずんだもん:} そうなのか?

\footnotesize \textcolor{pink}{四国めたん:} はい、形式はint rand()ですわ

\footnotesize \textcolor{lime}{ずんだもん:} 詳しく、教えてほしいのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、まず、使用するには"stdlib.h"ファイルをインクルードする必要がありますわ

\footnotesize \textcolor{lime}{ずんだもん:} ファイルの先頭に#include <stdlib.h>を記述するのだ

\footnotesize \textcolor{pink}{四国めたん:} そして、引数は無く、戻り値は0以上の整数で、呼び出す毎に異なる値が返りますわ

\footnotesize \textcolor{lime}{ずんだもん:} たしかに乱数っぽいのだ

\footnotesize \textcolor{pink}{四国めたん:} なお、戻り値として返る整数値の最大は、RAND_MAXとして"stdlib.h"内で定義されていますわ

\footnotesize \textcolor{lime}{ずんだもん:} RAND_MAXは、実際にはいくつなのだ?

\footnotesize \textcolor{pink}{四国めたん:} システムごとに異なりますが、Visual Studioの場合は0x7FFF、つまり、32,767ですわね

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} とりあえず実際のプログラムを見てみましょう

#include <stdio.h>
#include <stdlib.h>

void main() {
  for (size_t i = 0; i < 5; i++) {
    int num = rand();
    printf("%d ", num);
  }
  printf("\n");

  for (size_t i = 0; i < 5; i++) {
    int num = rand();
    int mod = num % 100;
    printf("%d ", mod);
  }
  printf("\n");

  for (size_t i = 0; i < 5; i++) {
    double num = rand();
    double norm = num / RAND_MAX;
    printf("%lf ", norm);
  }
  printf("\n");
}

乱数の出力

\footnotesize \textcolor{lime}{ずんだもん:} たしかにバラバラな数値が出力されているのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、最初のforループでは、取得した乱数をそのまま出力していますわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} 次のループでは、100で割った余り、つまり、0~99の範囲の整数の乱数として出力していますわ

\footnotesize \textcolor{lime}{ずんだもん:} お~、こうすれば、任意の整数の範囲の乱数を得られるのだ

\footnotesize \textcolor{pink}{四国めたん:} 負数も範囲に含める場合には、一定の数を差し引けばOKですわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} 最後のループは、取得した乱数を浮動小数点数に変換後、最大値で割って、0.0~1.0の範囲の浮動小数点数の乱数として出力していますわ

\footnotesize \textcolor{lime}{ずんだもん:} こうすれば、任意の範囲の浮動小数点数の乱数を得られるのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、範囲を拡げたければ、任意の数をかければ得られますわね

\footnotesize \textcolor{lime}{ずんだもん:} ところで、forループ内のインデックスが全て"i"になっているのだが...

\footnotesize \textcolor{pink}{四国めたん:} はい、forループで使用するインデックス"i"は、ループが異なれば、別個のインデックスとして扱われますわ

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

もう少しバラバラに...

\footnotesize \textcolor{pink}{四国めたん:} とりあえず、もう一度プログラムを実行してみましょう

再実行の結果

\footnotesize \textcolor{lime}{ずんだもん:} 当然、同じ結果を得ることができたのだ

\footnotesize \textcolor{pink}{四国めたん:} 乱数を使っているのに?

\footnotesize \textcolor{lime}{ずんだもん:} お~、そういえば乱数を使っているので、違う結果が出て欲しかったのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、出力された数値が前回と一緒なので、乱数としては失敗ですわ

\footnotesize \textcolor{pink}{四国めたん:} なにしろ、1回、プログラムを実行すれば、次からは得られる数値が予測できてしまうからですわ

\footnotesize \textcolor{lime}{ずんだもん:} どうしてなのだ?

\footnotesize \textcolor{pink}{四国めたん:} これは、rand関数が、内部では計算で乱数を取得しているからですわ

\footnotesize \textcolor{lime}{ずんだもん:} つまり、初期設定を変えなければ、同じ結果が出てしまうということか

\footnotesize \textcolor{pink}{四国めたん:} はい、計算を正確に行うコンピューターの良い面が、裏目に出てしまった結果ともいえますわね

初期値を変えましょう

\footnotesize \textcolor{lime}{ずんだもん:} どうにかならないのか?

\footnotesize \textcolor{pink}{四国めたん:} 実は、rand関数には、初期設定を変える関数srandがセットで提供されていますわ

\footnotesize \textcolor{lime}{ずんだもん:} どのような関数なのだ?

\footnotesize \textcolor{pink}{四国めたん:} 形式はvoid srand(unsigned int seed)となりますわね

\footnotesize \textcolor{lime}{ずんだもん:} 詳しく教えてほしいのだ

\footnotesize \textcolor{pink}{四国めたん:} 引数の"seed"に初期値となる値をセットするだけですわ

\footnotesize \textcolor{lime}{ずんだもん:} さっそく、srandを追加して確認してみるのだ

#include <stdio.h>
#include <stdlib.h>

void main() {
  srand(100);

  for (size_t i = 0; i < 5; i++) {
    int num = rand();
    printf("%d ", num);
  }
  printf("\n");

  for (size_t i = 0; i < 5; i++) {
    int num = rand();
    int mod = num % 100;
    printf("%d ", mod);
  }
  printf("\n");

  for (size_t i = 0; i < 5; i++) {
    double num = rand();
    double norm = num / RAND_MAX;
    printf("%lf ", norm);
  }
  printf("\n");
}

初期化した乱数

\footnotesize \textcolor{lime}{ずんだもん:} 前回とは異なる数値が出力されたのだ

\footnotesize \textcolor{pink}{四国めたん:} とはいえ、srandの"seed"にセットする値が同じであれば、毎回、同じ数値が得られることは変わりありませんわね

初期化した乱数2

\footnotesize \textcolor{lime}{ずんだもん:} ダメではないか!

時刻を初期値に

\footnotesize \textcolor{pink}{四国めたん:} この問題は、srandの"seed"に乱数で得られる値をセットすることで解消しますわ

\footnotesize \textcolor{lime}{ずんだもん:} 乱数を生成するのに乱数を使うのか???

\footnotesize \textcolor{pink}{四国めたん:} よく使われるのは、現在の時刻をsrandの"seed"にセットする方法ですわ

\footnotesize \textcolor{lime}{ずんだもん:} 時刻だと乱数にならないのではないか?

\footnotesize \textcolor{pink}{四国めたん:} たしかにプログラム中で時刻を取得する場合、 最初 以外は定期的な取得となるので、乱数にはなりませんわね

\footnotesize \textcolor{lime}{ずんだもん:} 最初 以外は?

\footnotesize \textcolor{pink}{四国めたん:} はい、人がプログラムを開始する時刻は、意図しない限りはランダムになりますわ

\footnotesize \textcolor{pink}{四国めたん:} ですので、最初に取得する時刻は、おおむね乱数として扱えますわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} なお、現在の時刻を取得するには、C標準関数のtimeを使用しますわ

\footnotesize \textcolor{lime}{ずんだもん:} どのような関数なのだ

\footnotesize \textcolor{pink}{四国めたん:} 形式はtime_t time(time_t* timer)ですわ

\footnotesize \textcolor{lime}{ずんだもん:} 詳しく教えてほしいのだ

\footnotesize \textcolor{pink}{四国めたん:} まず、使用するには"time.h"ファイルをインクルードする必要がありますわ

\footnotesize \textcolor{lime}{ずんだもん:} ファイルの最初に#include <time.h>と記述するのだ

\footnotesize \textcolor{pink}{四国めたん:} そして、"timer"に変数へのポインタをセットすると、その変数には戻り値と同じ値がセットされますわ

\footnotesize \textcolor{lime}{ずんだもん:} 同じ値が得られるのであれば、ムダではないのか?

\footnotesize \textcolor{pink}{四国めたん:} まぁ、通常はNULLをセットして問題ないと思いますわ

\footnotesize \textcolor{pink}{四国めたん:} そして、戻り値は現在時刻で、単位は"秒"ですわね

\footnotesize \textcolor{lime}{ずんだもん:} time_tとはなんなのだ?

\footnotesize \textcolor{pink}{四国めたん:} time_tは時刻を表す整数の型ですので、そのままsrand関数の引数にセットしても問題ありませんわ

\footnotesize \textcolor{lime}{ずんだもん:} とりあえず実例をおねがいするのだ

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void main() {
  srand(time(NULL));

  for (size_t i = 0; i < 5; i++) {
    int num = rand();
    printf("%d ", num);
  }
  printf("\n");

  for (size_t i = 0; i < 5; i++) {
    int num = rand();
    int mod = num % 100;
    printf("%d ", mod);
  }
  printf("\n");

  for (size_t i = 0; i < 5; i++) {
    double num = rand();
    double norm = num / RAND_MAX;
    printf("%lf ", norm);
  }
  printf("\n");
}

時刻で初期化した乱数

\footnotesize \textcolor{lime}{ずんだもん:} 実行するたびに得られる数値が異なるのだ

時刻で初期化した乱数2

timeについて

time関数で得られる値は、1970年1月1日0時0分0秒(UTC)からの経過時間で、秒単位です。

戻り値の time_t型 で表せる時間は29億年分くらいあるようなので、最大1秒以内にtime関数を再実行する以外では、同じ値を取得することはないでしょう。

まとめ

\footnotesize \textcolor{pink}{四国めたん:} お疲れさまでした

\footnotesize \textcolor{lime}{ずんだもん:} おつかれさまなのだ

\footnotesize \textcolor{pink}{四国めたん:} 以上で 乱数 についての説明は終わりですわ

Discussion