ActionScript3.0の乱数
Flashにおける乱数の精度はどれくらいなのかというのが気になりましたので、測定することにしました。
VC++のライブラリにおけるrand()はサンプルがそれほど多くない場合でもパターンが感じられる程、精度が低い物のようです。
cのrand
#define A 214013
#define C 2531011
#define F 0
#define S 1
static long x=S;
int rand() { x=x*A+C; return (int)(x>>16)&32767; }
void srand(long s) { x=s; if (F) rand(); }
たったこれだけのコードで乱数を発生させていたというのが驚きですね。
優れた乱数生成方法にMT法というものがあります。
phpの関数ではmt_randという関数がありますが、内部ではこのMT法が使用されています。
MT法を使用した乱数は精度が飛躍的に向上します。アルゴリズムが公開されていますので、それをFlashに移植したクラスを作成してみました。
MtRand
package
{
/**
/* @func : mt_rand移植クラス
/* @author : N2
/* @date : 2009/10/03 N2 作成
**/
public class MtRand
{
// 定数
private static const N:uint = 624;
private static const M:uint = 397;
private static const MATRIX_A:uint = 0x9908b0df;
private static const UPPER_MASK:uint = 0x80000000;
private static const LOWER_MASK:uint = 0x7fffffff;
private static const mag01:Array = [0x0, MtRand.MATRIX_A];
// 静的変数
public static var mt:Array = new Array(MtRand.N);
public static var mti:int = MtRand.N + 1;
/**
/* @brief : シード初期化
/*
/* @li :
/* @param :
/* @return :
/* @author : N2
/* @note :
/* @date : 2009/10/03 N2 作成
**/
public static function Initialize() : void
{
var date:Date = new Date();
var s:uint = date.time;
MtRand.mt[0] = s & 0xffffffff;
for (MtRand.mti=1; MtRand.mti<MtRand.N; MtRand.mti++) {
MtRand.mt[MtRand.mti] = (1812433253 * (MtRand.mt[MtRand.mti - 1] ^ (MtRand.mt[MtRand.mti - 1] >> 30)) + MtRand.mti);
MtRand.mt[MtRand.mti] &= 0xffffffff;
}
}
/**
/* @brief : ランダム数を取得
/*
/* @li :
/* @param : int : bet : ランダム数の最大値(±)
/* @return : int : ランダム数
/* @author : N2
/* @note : (-bet + 1)~0~(bet - 1)の間でランダムな整数を返す
/* : Mersenne Twister Home Page(mt_randの著者)からアルゴリズムを引用(AS3.0用に移植)
/* : URL:http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/mt.html
/* @date : 2009/10/03 N2 作成
**/
public static function GetRand(bet:int) : int
{
var y:uint;
if (bet == 0) {
return 0;
}
if (MtRand.mti >= MtRand.N) {
var kk:int;
if (MtRand.mti == MtRand.N + 1) {
MtRand.Initialize();
}
for (kk = 0; kk < MtRand.N - MtRand.M; kk++) {
y = (MtRand.mt[kk] & MtRand.UPPER_MASK) | (MtRand.mt[kk + 1] & MtRand.LOWER_MASK);
MtRand.mt[kk] = MtRand.mt[kk + MtRand.M] ^ (y >> 1) ^ MtRand.mag01[y & 0x1];
}
for (; kk < MtRand.N - 1; kk++) {
y = (MtRand.mt[kk] & MtRand.UPPER_MASK) | (MtRand.mt[kk + 1] & MtRand.LOWER_MASK);
MtRand.mt[kk] = MtRand.mt[kk + (MtRand.M - MtRand.N)] ^ (y >> 1) ^ MtRand.mag01[y & 0x1];
}
y = (MtRand.mt[MtRand.N - 1] & MtRand.UPPER_MASK) | (MtRand.mt[0] & MtRand.LOWER_MASK);
MtRand.mt[MtRand.N - 1] = MtRand.mt[MtRand.M - 1] ^ (y >> 1) ^ MtRand.mag01[y & 0x1];
MtRand.mti = 0;
}
y = MtRand.mt[MtRand.mti++];
y ^= (y >> 11);
y ^= (y << 7) & 0x9d2c5680;
y ^= (y << 15) & 0xefc60000;
y ^= (y >> 18);
// 引数が負数なら マイナスbetを超える~0 にする
if( bet < 0 ) {
bet = -bet;
return -int(y % bet );
}
return y % bet;
}
}
}
比較用にMath.randomを使用した物も貼っています。
package
{
// SYSTEM PACKAGE
import flash.display.*;
import flash.text.TextField;
/**
/* @func : ランダム関数確認用
/* @author : N2
/* @date : 2009/10/03 N2 作成
**/
public class rand extends Sprite
{
/**
/* @brief : コンストラクタ
/*
/* @li :
/* @param :
/* @return :
/* @author : N2
/* @note :
/* @date : 2009/10/03 N2 作成
**/
public function rand()
{
// 設定(modeは0 or 1)
var mode:uint = 0;
var loop:uint = 1000000;
var w:uint = this.stage.stageWidth;
var h:uint = this.stage.stageHeight;
var color:uint;
// ローカル変数
var i:uint;
var max:uint = 0;
var num:Array = new Array();
var sp:Shape;
switch (mode) {
case 0: // mt_rand
MtRand.Initialize();
for (i = 0; i < loop; i++) {
num[i] = 0;
}
for (i = 0; i < loop; i++) {
num[MtRand.GetRand(w)]++;
}
for (i = 0; i < w; i++) {
if (num[i] > max) {
max = num[i];
}
}
// カラー設定
color = 0xffff00;
break;
case 1: // Math.random
for (i = 0; i < loop; i++) {
num[i] = 0;
}
for (i = 0; i < loop; i++) {
num[Math.floor(Math.random() * w)]++;
}
for (i = 0; i < w; i++) {
if (num[i] > max) {
max = num[i];
}
}
// カラー設定
color = 0x00ff00;
break;
}
// グラフ出力
sp = new Shape();
sp.graphics.lineStyle(1, color);
for (i = 0; i < w; i++) {
sp.graphics.moveTo(i, h);
sp.graphics.lineTo(i, h - ((num[i] / max) * h));
}
sp.graphics.endFill();
this.addChild(sp);
// 最大値出力
var tf:TextField = new TextField();
tf.text = "最大値:" + max;
tf.x = 0;
tf.y = 380;
this.addChild(tf);
}
}
}
MtRandクラス※要FlashPlayer
Math.random()クラス※要FlashPlayer
乱数を0~399の間で1000000回作成しています。作成した乱数はそれぞれ配列の0~399に振り分けた後、最大値を取得します。
最大値を100%とし、0px~399pxの1px間隔ずつグラフを描画しています。
つまり山が平坦な程、均等に乱数が発生していると言えます。
結論から言うと、どちらも精度に差がありませんのでMath.random関数内でもMT法を用いているのではないでしょうか?
即ち、ActionScript3.0ではmt_randを自作する必要はないということです。