シード付き擬似乱数 Xorshift

シード付きで擬似乱数を生成する軽量アルゴリズム「Xorshift」を扱うための関数です。 2018-06-28

 シード付き擬似乱数を生成する関数のコードを掲載します。ドキュメントは「Xorshiftドキュメント」をご覧下さい。

 多くの擬似乱数の関数では、シード(計算の種になる初期の値)を設定して、得られる値をコントロールできます。しかし、JavaScriptの標準ランダム関数「Math.random」は、シードを設定できません。そのため、実行するごとに、どのよな値が得られるかは分かりません。

 ゲームを開発している時には、デバッグの再現性を確保するために、擬似乱数のシードは設定したいところです。シードが設定できるか否かで、デバッグの用意さは大きく変わります。

 そこで私は、Xorshiftアルゴリズムを利用した擬似乱数の関数を用意して、これを使っています。

 擬似乱数には、様々なアルゴリズムがあります。有名な所では、線形合同法やメルセンヌ・ツイスタなどがあります。

 線形合同法は、実装が非常に簡単で計算コストも安いために、非常に多くの場所で利用されていますが、乱数としての質はそれほどよくありません(すぐに同じ周期で値が出るようになる、値の設定によっては偏りが酷い)。

 メルセンヌ・ツイスタは、乱数としての質が非常によいのですが、計算コストはかかります。

 Xorshiftは、乱数としての質はメルセンヌ・ツイスタほどではないのですがよく、計算コストも安いです。実装も簡単なので、自分で疑似関数を追加するには、非常に向いています。

 ファイル構成は以下の通りです。>>ダウンロード

※ 初期化用関数「init」の処理については「Webブラウザ/Node.js 互換関数」をご覧下さい。

サンプル動作

sample/xorshift.js

'use strict';

(function() {
	const _t = init('com.crocro.util');

	// Xorshift | シード付き擬似乱数
	// new Xors(n);でオブジェクトを作成して使います。
	_t.Xorshift = function(n) {
		let x, y, z, w;

		// シード
		this.seed = function(n) {
			x = 123456789; y = 362436069; z = 521288629; w = 88675123;
			if (typeof n === "number") {w = n;}
		}

		// ランダム
		this.rnd = function() {
			const t = x ^ (x << 11);
			x = y; y = z; z = w;
			return w = (w^(w>>19))^(t^(t>>8));
		}

		// 初回実行
		this.seed(n);
	};

	// 初期化用関数
	function init(p) {
		try {return module.exports = {}} catch(e) {}
		return ((o, p) => {
			p.split('.').forEach(k => o = o[k]||(o[k]={}));
			return o})(window, p);
	};
})();

解説

 この関数は、「const xors = new Xorshift(n)」のように、Xorshiftオブジェクトを作成してから、そのオブジェクトのメソッド「xors.rnd()」を利用して値を得ます。シードの値「n」を省略した場合は、デフォルトの値が利用されます。

 「rnd()」を実行するごとに、内部の値が更新されて、新しい擬似乱数を得られます。得られる値は、JavaScript組み込みの「Math.random()」とは違い、整数です。

 「Math.random()」は0以上1未満の浮動小数点数が得られ、そこに得たい範囲の数字を掛けて整数化して使います。「Math.floor(Math.random() * 16)」のようにします。

 「xors.rnd()」で得られる値は、桁の大きな整数です。そのため、剰余を取って利用します。「xors.rnd() % 16」のようにします。

 「Math.random()」と「xors.rnd()」では、このように使い方に違いがあります。

 擬似乱数は、前に計算した時の値に依存して、次の計算結果が決まります。値が得られる順番は、シードが同じ場合は必ず同じです。完全なランダムではなく、擬似乱数と呼ばれるゆえんです。

 Xorshiftオブジェクトは、複数作ることができます。ゲームでは、複数の場所で擬似乱数を用います。複数の擬似乱数オブジェクトを作ることで、攻撃用の擬似乱数、防御用の擬似乱数のように、擬似乱数の対応を分けることができます。

 そうすることで、「3回目の攻撃でバグが出た」のように、不具合の検証を容易にできます。


 以下、Xorshiftで擬似乱数を多く得てみます。試行回数を100回、1000回、10000回と増やせば、それぞれの値が出た数が均されていきます。

sample2/node_sample.js

// モジュールの読み込み
const Xors = require('./xorshift.js').Xorshift;

// 実行

const test = (max, veiwRate) => {
	const xors = new Xors();
	const arr = [];
	const sz = 10;

	for (let i = 0; i < max; i ++) {
		const r = xors.rnd() % sz;
		arr[r] = arr[r] ? arr[r] + 1 : 1;
	}

	console.log('max =', max);
	arr.forEach((x, i) => {
		console.log(i, '*'.repeat(x * veiwRate));
	});
	console.log('');
};

test(100, 1);
test(1000, 0.3);
test(10000, 0.03);

 出力結果です。

max = 100
0 '**********'
1 '*******'
2 '************'
3 '**********'
4 '*************'
5 '******'
6 '*****************'
7 '********'
8 '*******'
9 '**********'

max = 1000
0 '*****************************'
1 '********************************'
2 '***************************'
3 '******************************'
4 '********************************'
5 '*****************************'
6 '**************************************'
7 '************************'
8 '***************************'
9 '***************************'

max = 10000
0 '******************************'
1 '*****************************'
2 '*****************************'
3 '*****************************'
4 '******************************'
5 '*****************************'
6 '******************************'
7 '******************************'
8 '******************************'
9 '******************************'

Xorshift | シード付き擬似乱数

シード付きの擬似乱数です。アルゴリズムはXorshiftです。
new Xors(n);でオブジェクトを作成して使います。

@param {Integer} n - 擬似乱数のシード。
このシードの値を元にして、擬似乱数を回します。

@return new Xors(n);とすることで、Xorsオブジェクトを作成します。

以下は、new Xors(n);で作成した、Xorsオブジェクトのメソッドです。

 xors.rnd()


擬似乱数を得ます。戻り値は整数ですので、剰余を取るなどして使います。

 xors.seed(n)


シードを設定し直します。

@param {Integer} n - 擬似乱数のシード。
このシードの値を元にして、擬似乱数を回します。

サンプル

// Webブラウザ
const Xors = com.crocro.util.Xorshift;
const xors = new Xors();
console.log(xors.rnd(), xors.rnd(), xors.rnd());
	// 597902826 458295558 1779455562
console.log(xors.rnd() % 16, xors.rnd() % 16, xors.rnd() % 16);
	// 0 14 6

// Node.js
const Xors = require('./xorshift.js').Xorshift;
const xors = new Xors();
console.log(xors.rnd(), xors.rnd(), xors.rnd());
	// 597902826 458295558 1779455562
console.log(xors.rnd() % 16, xors.rnd() % 16, xors.rnd() % 16);
	// 0 14 6

sample/index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>シード付き擬似乱数 Xorshift - JavaScript実用サンプルコード解説付き</title>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js">
    </script>

    <script src="xorshift.js"></script>
    <script src="main.js"></script>
    <link rel="stylesheet" href="main.css">
  </head>
  <body>
    <textarea id="src"></textarea>
    <button id="exec">実行</button>
    <textarea id="dst"></textarea>
  </body>
</html>

sample/main.js

'use strict';

$(function() {
	// 変数の初期化
	const $src  = $('#src');
	const $dst  = $('#dst');
	const $exec = $('#exec');

	// 値のセット
	$src.val(String.raw`
const Xors = com.crocro.util.Xorshift;
const xors = new Xors();
console.log(xors.rnd(), xors.rnd(), xors.rnd());
console.log(xors.rnd() % 16, xors.rnd() % 16, xors.rnd() % 16);
	`.trim());

	// コンソール ログのラップ
	const log = console.log;
	console.log = function() {
		for (let i = 0; i < arguments.length; i ++) {
			$dst.append(arguments[i] + ' ');
		}
		$dst.append('\n');
		log.apply(console, arguments);
	};

	// クリック時の処理
	$exec.click(() => {
		$dst.empty();
		eval($src.val());
	});
});

sample/main.css

html,
body {
	margin: 0;
	padding: 0;
}

textarea,
button {
	box-sizing: border-box;
	width: 100%;
	vertical-align:bottom;
}
textarea {
	height: 5em;
}

sample/node_sample.js

// モジュールの読み込み
const Xors = require('./xorshift.js').Xorshift;

// 実行
const xors = new Xors();
console.log(xors.rnd(), xors.rnd(), xors.rnd());
console.log(xors.rnd() % 16, xors.rnd() % 16, xors.rnd() % 16);

紹介

Steamでゲームをリリースした時の経験をマニュアル的にまとめた本です。
8bit風RTS「TinyWar」のアルゴリズムを、コード付きで解説した本です。
JavaScriptから手軽に扱える形態素解析器『kuromoji.js』を使い、日本語を分解して遊ぶ本です。
node.jsを使い、「Google Chrome」のユーザーデータを、自動処理でメンテナンスするプログラムを開発する本です。
HTML5でローカルアプリが作れるNW.jsで、同人ゲームを作るための基礎知識の本です。
シミュレーションRPG「TinySRPG」のアルゴリズムを、コード付きで解説した本です。
JavaScriptの実行時エラーを分類して、ワンライナーのソースコードとエラーメッセージを収録した本です。
禁止文字つきコードゴルフを1年以上出題して、その解答ノウハウをまとめた本です。
2011年の春ごろに、Javaで「NyARToolKit」互換のARマーカー認識プログラムを書いた時のレポートです。