2014年9月6日土曜日

擬似乱数が話題になっていたので、MT布教のためのJava Classサンプル公開

Mersenne Twister(MT) は非常に簡単な処理で恐ろしく周期が長い、というか周期が無いとしても差し支えない優秀な擬似乱数生成アルゴリズムです。
詳しくはこちらの本家を御覧ください。
Mersenne Twister: A random number generator (since 1997/10)

僕は初期のMTを知り感動して以来、C++、Java、C#のクラスに実装しずっと使わせて頂いています。
コンピュータの乱数の話でこのMTを語らない方が多くいつもモヤモヤしてましたので、自前の移植クラスを公開してみます。

ゲームにおいてですが、32ビットCPU使えるならこれ以外の擬似乱数は必要ありません。というかこれ以前の生成方法は特別な状況以外価値がなくなりましたので、わざわざ使わない方が良いですね。

実はJavaのクラスライブラリには随分前にMTが採用されているのですが、ゲームプログラムにおいては自前クラスで実装しておいた方が何かと便利です。ゲームプログラミグ独特かもしれませんが、C++のSTLと同様用意されたものを使わず自前実装した方が有利なカテゴリと言えますね。


public class MT {
public final static long Max = 0xffffffff;
final static int MT_N = 624; // length of state vector
final static int MT_M = 397; // a period parameter
final static long MT_MATRIX_A = 0x9908B0DFL; // constant vector a

long [] sv = new long[MT_N]; // state vector
int N, M; // random value is computed from here

public MT(long _s) {
N = 0;
M = MT_M;
for (int i = 0; i < MT_N; i++)
sv[i] = _s = ((1812433253L * (_s ^ (_s >> 30)) + (long)(i))) & 0xffffffffL;
}

public long Next() {
int P = N;
if (++N == MT_N) N = 0;
// move hi bit of u to hi bit of v
sv[P] = sv[M] ^ (((sv[P] & 0x80000000L) | (sv[N] & ~(0x80000000L))) >> 1) ^(((sv[N] & 1L) != 0)? MT_MATRIX_A: 0);
if (++M == MT_N) M = 0;
// Tempering
long y = sv[P];
y ^= (y >> 11);
y ^= (y << 7) & 0x9D2C5680L;
y ^= (y << 15) & 0xEFC60000L;
y ^= (y >> 18);
return ((long)(y));
}

public long NextL(long _max) { return (Next() % (_max +1)); }
public long NextL(long _min, long _max) { long range = _max - _min +1; return (Next() % range + _min); }
public int Next(int _max) { return ((int)(Next() % (_max +1))); }
public int Next(int _min, int _max) { int range = _max - _min +1; return ((int)(Next() % range + _min)); }

public float Next(float _max) { return ((float)(Next())*_max/0x100000000L); }
public float Next(float _min, float _max) { float range = _max - _min; return ((float)(Next())*range/0x100000000L +_min); }
}



これだけ、コンストラクタと基本のメソッド1つです。
コンストラクタに適当なシードを渡すと、古い擬似乱数でMTの初期ベクタを作ります。Next()を呼ぶ度に撹拌されたlong値の擬似乱数が返ります。
Next()メソッドがMTの本体ですが、この様な単純な演算の組み合わせだけで2の19937乗-1というとんでもない周期の乱数が手に入ります。
出力もlong以上が欲しければ、複数回取ってきて組み合わせるだけです。4個一組にしても、とんでもない周期が1/4になるだけですから。

Next()メソッドの変種が色々付いてますが、この処理はゲーム用に簡略化しています。皆さん用途に合わせて適当に作られれば宜しいかと。

初期ベクタの設定の仕方で様々な応用ができますので、コンストラクタも用途に合わせてアレンジするのが良いかと思います。
アナログ的な乱数として使用するなら、シードを現在時刻から生成すれば良いでしょう。
但し出力段(Next)の撹拌性能に由来する偏りも知られていますので、気になる方は対策を調べられるよと良いかと思います。

ちなみに元のC言語版ではCPUのキャッシュ内での実行による高速化等が考慮されていたのですが、今時のゲーム用途ではさほど必要無いのでシンプルな処理に直しました。

このMTがゲームにおいてとんでもない武器だという事を、一人でも多くの方に知っていただきたいものです。僕なりの使い道の例はその内に。

最後ですが、MTのサイトにある以下のページは、是非とも御覧ください。数学研究に対する考えが少し変わるかと思います。


お願いと謝辞


0 件のコメント:

コメントを投稿