第19回 乱数

何かを適当に選ぶにはどのようにしたらよいのでしょうか。今回はプログラムで適当に何か選ぶ方法について習得します。

目次

乱数

Arduinoでは、適当な数字を選んでくれる命令が用意されています。

例えば1から6までの適当な数字を選んでもらえれば、サイコロを使ったゲームを作ることができます。他にも、例えば1〜3までの適当な数字を選んでもらって、if文を使用して数字に応じた文字列を選ぶ、ということもできます。

このように適当な数字を得ることができればゲーム性のあるプログラムを作ることができます。

ところで「適当な数字選ぶ」といっても、実際にどのような動きがよくわからないですよね。

もう少し具体的に説明すると、Arduinoでは「〇〜□までの数字を適当に選んでください」(〇と□は数字)という命令が用意されています。

例えば「1〜9までの数字を適当に選んでください」という命令を実行すると、「3」とか「7」とかその命令を実行するたびに適当な数字を選んでくれます。

このような適当な数字のことを「乱数」と呼んでいます。命令を実行するたびに毎回異なる適当な数字が選ばれるので、それを乱れた数という意味合いで「乱数」と呼んでいます。(「適当数」の方が合ってるような気もしますが…)

また適当な数字を選んでもらうことを、コンピュータの世界では「乱数を生成する」とか「乱数を発生させる」などと言っています。

ところで「乱数」は英語で「Random Number」といいます。「Random」(ランダム)は「無作為」とか「行き当たりばったり」というような意味ですが、日本語でも「ランダム」というのは外来語として定着していますよね。この「Random」という単語はこのあとすぐに出てきますので、覚えておいてください。

それでは、これからこの乱数を生成する方法とその特徴について詳しくみていくことにしましょう。

random関数

それではさっそく乱数を生成する命令を確認します。この命令は2種類の書き方があります。

最初の書き方です。

random命令(1)

例えば、以下のようなプログラムを書くと、

int32_t number;

number = random(10);

numberという変数には「0〜9」までのいずれかの数字が代入されます。

ちょっとややこしいのですが、random(10);と書いた場合、「1〜10」までの数字ではなく「0〜9」までの数字、という点に注意してください。

ちょっと普通の感覚とは違いますよね。この背景について補足しておきます。

普段の生活では数を数える時は、「1、2、3…」というように1から数えますよね。でもコンピュータの世界では普通は0から数えます。これはなぜかというと、数字を有効に使用するためです。

例えばuint8_t型の変数は「0〜255」の256個の数字を代入できますが、これを1から数えてしまうと「1〜255」までの255個の数字を扱うことになってしまいます。そのため、数字は基本的には0から数えます。(for文で一定回数繰り返し処理をする場合、普通0からカウントします)

となると、random(10);と書いた場合、「0〜10」までの乱数を生成するように思いませんか?

実はrandom命令の引数は、0から始めて何個分の乱数を生成するか、という意味になっています。

つまりrandom(10);と書いた場合、「0から10個分の整数を生成する」という意味になります。0から10個の数字という場合、「0〜9」ですよね。

このように、random(数値);という命令は、「0 〜 数値-1」の範囲の数値個分の整数を返り値にしています。

次に、もう一つの書き方です。この書き方の場合、生成する乱数を0からではなく例えば3〜15、というようにある範囲で生成する命令になります。

random命令(2)

この場合、生成される乱数は「最小値 〜 最大値-1」の範囲の整数になります。例えばrandom(5, 10);と書いた場合、「5 〜 9」の乱数を生成してくれます。この命令もちょっとややこしいですね。

それでは実際に乱数を発生させてみましょう。

乱数を表示する

以下のスケッチで、乱数を生成してシリアルモニタで確認してみることにましょう。

それでは、Arduino MicroをPCに接続して、Arduino IDEで新規スケッチを作成、シリアルモニタを開いてください。

次に以下のスケッチをコピペしてArduino Microに書き込みましょう。

/*
 * random関数の動作確認用スケッチ
 */

void setup() {
  // シリアルモニタ初期設定
  Serial.begin(9600);
  while(!Serial){  
  }

  // 乱数を30個生成してシリアルモニタに表示する
  for(uint8_t count=0; count<30; count++){
    Serial.println( random(10) );
  } 

}

void loop() {

}

このスケッチでは、random(10);としていますので、毎回適当に選んだ0〜9の数字をシリアルモニタに表示してくれます。

書き込みが終わると、以下にようにシリアルモニタに生成した30個の乱数が表示されます。

randomの30回の結果

結果を見ると、確かにランダムな数字、という感じですよね。この数字をもう少し詳しく確認してみましょう。確認するために、今シリアルモニタに表示されいる数字をテキストエディタなどにコピペしておいてください。

予測ができる乱数?

それではもう一度、Arduino Microに乱数を生成してもらいましょう。

なお、このまま再度スケッチを実行すると、現在のシリアルモニタに続けて表示されてしまいます。区切りがわからなくなってしまいますので、一度シリアルモニタの表示内容を消去しておきましょう。

シリアルモニタのウィンドウの右下に「出力をクリア」というボタンがありますので、そのボタンをクリックしてください。シリアルモニタの表示内容が消去されます。

それでは、再度スケッチを実行して乱数を生成してもらうために、Arduino Microをリセットします。

Arduino Microは以下の場所にリセットスイッチがありますので、一度スイッチを押してください。

リセットスイッチ

しばらく待っていると、シリアルモニタに乱数が表示されます。

シリアルモニタに表示された乱数を確認してみましょう。先ほどコピペしておいた乱数と、今回生成した乱数を比較してみてください。

参考に、3回乱数を生成した結果は以下になります。

生成した乱数の比較

確かに1回目は予測できない30個の数字が表示されたと思っていましたが、2回目と3回目もまったく同じ数字じゃないですか!

これって乱数っていうのでしょうか。毎回同じなので、乱数といっても1回確認しておけば予測できますよね。

これはどういうことなんでしょうか。

擬似乱数

コンピュータは厳密な計算を行う機械です。ランダムな数字を生成する、ということは原理的に不可能です。

そのため、コンピュータは乱数を「計算して」生成しています。「乱数を計算して生成する」というのも意味がよくわからないですよね。

そこで、乱数の生成方法の具体例を説明します。なお、これは説明を簡単にするための例で、実際の乱数の生成方法もっと複雑になっています。

乱数を生成するには、はじめに最初の数字を決めます。ここでは5とします。

最初の数字を決めたら、以下の数式を使って次の数字を決めます。

次の数字 = ( 3 x 最初の数字 + 5 ) を13で割った余り

最初の数字は5ですので、(3 x 5 + 5)を13で割った余りで7になります。

次にこの数字を使って先ほどと同じ数式で次の数字を決めます。

次の数字 = ( 3 x 前回の結果 + 5) を13で割った余り

前回の結果は7でしたので、( 3 x 7 + 5 )を13で割ったあまりで、0になります。

この後は上の式を使って次々に数字を計算していきます。このように計算していくと、

5、7、0、5、7、0…

ちょっとだけ乱数っぽい数字が得られます。ただこの数字ですと0、5、7以外の数字は出てきませんし、たった3つの数字のパターンの繰り返しになってしまっています。乱数とは程遠いですが、原理的にはこのような手順で乱数を計算して生成しています。実際の乱数生成に使われる計算式は、すべての数字がなるべくランダムにあらわれるように複雑な計算式を使います。

手順をまとめると、コンピュータの世界では、以下の手順で計算して乱数を生成しています。

  1. 最初の数字を決める
  2. 計算式を使用して(1)で決めた数字から次の数字を計算する
  3. 計算式を使用して(2)の結果から、次の数字を計算する
  4. 計算式を使用して(3)の結果から、次の数字を計算する
  5. 以下繰り返し

この仕組みは、Arduinoに限らず、どのプログラムミング言語、どのコンピュータでもこのような「なんちゃって乱数」を生成しています。

ただ「なんちゃって乱数」という呼び方はどうか、ということで正式には「擬似乱数」と呼んでいます。

本当の乱数

擬似乱数は結局は計算式で算出していますので、毎回同じ計算結果になりますし、パターンを繰り返します。本当の乱数ではないんです。

でも、何かの実験をしたりシミュレーションをするときには本当の乱数が必要なケースってありそうですよね。

そのような場合はどうするかというと、乱数を発生させる機器を作ります。

実際に本当にランダムな乱数を作るというのはかなり難しく、必ずパターンが出てきてしまったり、かたよりが出てきてしまいます。

そこで、多くの企業や研究機関によって、本当の乱数を発生する機器が日々研究されています。

先程説明したように、プログラムでは本当の乱数を生成することはできませんので、自然の原理を利用した乱数発生器が開発されています。ご興味があれば、ネットで「乱数発生器」を調べてみてください。

ところで本当の乱数を生成するには、自然現象を利用した機器を作るわけですが、研究により不思議な現象も確認されています。

不思議な現象というのは「人間の意識が乱数発生器に影響を与える」というものです。

都市伝説っぽい話ですが、この現象については、優秀な研究者を多く輩出している、かの有名なプリンストン大学で長年研究されています。「WIRED」という雑誌のサイトに記事がありますので、ご興味があればご覧ください。

「心が機械に影響を与える」プリンストン大学の研究

また、現在でも研究が続けられていて、その様子はプリンストン大学のサイトで公開されています。以下のサイトでは、世界の各所に設置されている乱数発生器で生成される乱数に人間の意識がどの程度作用するのか、リアルタイムで公開されています。

The Global Consciousness Project Meaningful Correlations in Random Data

ということで、本当に本当の乱数を発生させる、というのはこの宇宙では無理なのかもしれないですね。

randomSeed命令

話がずいぶん逸れてしまいましたが、これまで説明したようにソフトウエアの世界では乱数は計算で生成します。その結果、毎回同じパターンになることがわかりました。

このような仕組みだから、と言われても、毎回同じ組み合わせの数字は困りますよね。例えばArduino Microで、じゃんけんゲームやおみくじを作る場合、初めてゲームをする場合はいいとして、何度もやると同じパターンがバレてしまいゲームになりません。これはなんとか解決する必要があります。

当然ながらArduinoに限らずコンピュータの世界ではこの問題を解決する命令が用意されています。

最初にこの問題を解決する原理を説明します。

先ほど、乱数を発生させる方法として数式を使う説明をしました。その際、手順(1)で最初の数字を決めましたよね。この数字を違うものにすると、その後計算される数字は異なってきます。

先ほどの例では最初の数字を5にして計算しました。最初の数字が5の場合、生成される乱数(?)は、5、7、0の繰り返しでした。

この最初の数字を8に変えてみます。

( 3 x 最初の数字 + 5 ) を 13で割った余り

最初の数字が8の時、計算結果は9になります。次の数字はこの結果の9を使って計算しますので、同様に計算していくと、2、1、8、となります。

つまり、先ほどと同じ計算式を使っても、最初の数字を5から8に変更すると、計算結果は「8、9、2、1、8…」という数字の繰り返しになります。

つまり最初の数字を変えるとその後に生成される乱数は変わってくるわけです。

コンピュータの世界ではこのように乱数を計算するときの最初の数字を指定する命令が用意されています。この最初の数字は、これから生成する乱数のおおもとになるため「Seed」(シード)と呼ばれています。日本語では「種」という意味になり、この種を元にこの後の乱数を育てていく、というようなイメージでしょうか。また、シードを指定することを「乱数の初期化」とも呼ばれます。

Arduinoでは乱数を初期化するために以下の命令が用意されています。

randomSeed

randomSeed命令の引数に適当な数字を指定すると、乱数を計算するときの最初の数字(シード)を変更することができるわけです。

ところで、この命令を使用する場合に例えば

randomSeed(35);

などと、具体的な数字を指定してしまうと、結局毎回同じ乱数になってしまいますよね。この例では、最初の数字を35にして、計算式により計算していくわけですから、結局毎回同じ乱数になってしまいます。

つまり、randomSeed命令の引数は適当な数字にする必要があります。

毎回違う適当な数字が欲しいのに、そのためには適当な数字が必要、ってどうすればいいのでしょうか。

例えば、以下のようにrandomSeed命令の引数にrandom命令を使うのはどうでしょうか。

randomSeed( random(10) );

この場合、random(10)は先ほど実際のArduino Microで確認したように最初の数字は必ず7でした。ということは、この命令を実行しても、シードを7に設定するだけで、結局は毎回同じ乱数になってしまいます。

とても解決できない問題に見えますが、実はArduioでは定番の乱数初期化方法があります。

この方法については、新しい命令が必要になりますので、次回の記事で詳しく説明します。

更新履歴

日付 内容
2021.10.24 新規投稿
通知の設定
通知タイミング
guest
0 コメント
本文中にフィードバック
全てのコメントを見る
目次