JavaScriptの乱数は少し貧弱で、シード値の指定ができないなどの制限があります。普段は問題ないのですが、特定の用途では問題となる場合があります。シード値を指定可能な、再現性のある乱数を自作してみましょう。

JavaScript標準の乱数

もちろんJavaScriptにも乱数を発生させる機能はあります。Math.random()がそれにあたります。

Math.random()は0.0以上1.0未満の乱数を生成します。しかしこの乱数には問題があります。シード値を指定できず、実行ごとに異なる値が生成されます。

……「乱数」なんだからそれでいいんじゃないの?と思うかもしれません。しかしこれは特定の領域において問題となります。

例えば研究のためにJavaScriptでプログラムを組んだとします。その中で乱数を使って素晴らしい結果を出しました。しかし乱数を再現できないので、二度とその結果を再現することができません!

他にも、例えばゲームを製作しているとき、リプレイ機能を実装しようとすることを考えてみてください。実際にプレイしたときの乱数と、リプレイ時の乱数が異なっていると、正常にリプレイを再生できません!

シード値を指定可能な乱数生成器

そこで必要になってくるのが、乱数を生成するためのシード値を指定することができる乱数生成器です。これを自作してみましょう。

乱数の生成アルゴリズムには、線形合同法やメルセンヌツイスタなど様々なものがありますが、今回はXorShiftを選択しました。XorShiftは、簡単なコードで実現でき、なおかつ高速で、そこそこ質の高い乱数を生成してくれるアルゴリズムです。

XorShiftの実装は、たったこれだけです!x, y, z, wには任意の値をセットします。ただし全ての値がゼロの場合は正常に動作しません。そして、その4つの変数の中から適当にひとつ選び出し、シード値をセットします。あとはアルゴリズム通りに計算する(nextメソッドの中身)だけで完成です。

ひとつ注意点として、オリジナルのXorShiftでは符号無し整数を使っていますが、JavaScriptには符号無し整数はありません。したがってC言語などとは異なる結果になることがあります。例えばJavaScriptのXorShiftでは負の数が出てくる可能性があります。

さて、準備ができたら実際に使用してみましょう。以下のように使用します:

これを実行すると、だいたい以下のような結果になります:

乱数列を生成できました!そしてシード値が同じなら、何度やっても同じ乱数列が得られます。もちろん異なるシード値を用いれば異なる結果を得ることができます。

また、Math.random()のように実行するたびに異なる値を得たいのならば、Date.now()の数値をシード値として使うといいでしょう。

任意の範囲の乱数を得る

このようにして得られた乱数には、まだ問題があります。出力される値が全くのデタラメで、場合によっては使いづらいことがあります。例えば1から10までの間の乱数が欲しいのに、504890836という値が出てくるかもしれません!

そこで少し手を加えて、任意の範囲の値が得られるようにしましょう。Randomクラスに、任意の範囲の値を出力するnextInt()メソッドを実装します。

簡単ですね!しかしなにやら少しややこしい計算が出てきました。これは一体なにをやっているのでしょう?

nextInt()メソッドの中では、まず通常通りに乱数を生成しています。そしてマイナスが出てきたときに消すために、その絶対値をとっています。これをrとします。

次に、最低でもminとなる乱数が欲しいので、minの値をベースに計算していきます。

そして乱数rを(max + 1 – min)で割った余りを求めてminに足しています。「余り」というのは面白いもので、例えばr % 10は必ず0〜9の範囲になります。なぜなら10で割ったとき、余りが10以上になることはないからです。もし10以上の余りがあるのなら、さらに10で割れますからね。

では、さて(max + 1 – min)とはなんでしょう?まず、r % (max + 1)を考えてみましょう。これはrを(max + 1)で割った余りなので、結果は0〜maxになります。そしてそこからminだけ引いているので、r % (max + 1 – min)の結果は0〜max-minとなります。

そしてmin + (r % (max + 1 – min))なので、min + (0〜max-min)となり、これを計算するとmin〜maxとなります。つまり、最小値から最大値の間の値が得られる、ということです。

min + (r % (max – min + 1))の謎は解けました!あとは使ってみるだけですね。

このとき以下のような出力が得られます:

これで任意の範囲の乱数を得ることができました。