Home Reference Source

src/konpeito/Random.js

/**
 * The script is part of konpeito.
 * 
 * AUTHOR:
 *  natade (http://twitter.com/natadea)
 * 
 * LICENSE:
 *  The MIT license https://opensource.org/licenses/MIT
 */

/**
 * Multiply two 32-bit integers and output a 32-bit integer.
 * @param {number} x1 
 * @param {number} x2 
 * @returns {number}
 * @private
 * @ignore
 */
const multiplication32 = function(x1, x2) {
	let y = ((x1 & 0xFFFF) * (x2 & 0xFFFF)) >>> 0;
	let b = (x1 & 0xFFFF) * (x2 >>> 16);
	y = (y + ((b & 0xFFFF) << 16)) >>> 0;
	b = (x1 >>> 16) * (x2 & 0xFFFF);
	y = (y + ((b & 0xFFFF) << 16)) >>> 0;
	return y;
};

/**
 * 乱数を扱うクラス
 */
export default class Random {
	
	/**
	 * 初期化
	 * @param {number} [seed] - Seed number for random number generation. If not specified, create from time.
	 */
	constructor(seed) {

		/**
		 * @type {number}
		 * @private
		 * @ignore
		 */
		this.x = 123456789;
		
		/**
		 * @type {number}
		 * @private
		 * @ignore
		 */
		this.y = 362436069;
		
		/**
		 * @type {number}
		 * @private
		 * @ignore
		 */
		this.z = 521288629;
		
		/**
		 * @type {number}
		 * @private
		 * @ignore
		 */
		this.w = 88675123;

		if(seed !== undefined) {
			this.setSeed(seed);
		}
		else {
			// 線形合同法で適当に乱数を作成する
			const new_seed = ((new Date()).getTime() + Random.seedUniquifier) & 0xFFFFFFFF;
			Random.seedUniquifier = (Random.seedUniquifier + 1) & 0xFFFFFFFF;
			this.setSeed(new_seed);
		}
	}

	/**
	 * シード値の初期化
	 * @param {number} seed
	 */
	setSeed(seed) {
		// seedを使用して線形合同法で初期値を設定
		let random_seed = seed;
		random_seed = (multiplication32(random_seed, 214013) + 2531011) >>> 0;
		this.z = random_seed;
		random_seed = (multiplication32(random_seed, 214013) + 2531011) >>> 0;
		this.w = random_seed;

		/**
		 * Is keep random numbers based on Gaussian distribution.
		 * @private
		 * @type {boolean}
		 */
		this.haveNextNextGaussian = false;
		
		/**
		 * Next random number based on Gaussian distribution.
		 * @private
		 * @type {number}
		 */
		this.nextNextGaussian = 0;
	}

	/**
	 * 32-bit random number.
	 * @returns {number} - 32ビットの乱数
	 * @private
	 */
	genrand_int32() {
		const t = this.x ^ (this.x << 11);
		this.x = this.y;
		this.y = this.z;
		this.z = this.w;
		this.w = (this.w ^ (this.w >>> 19)) ^ (t ^ (t >>> 8));
		return this.w;
	}

	/**
	 * 指定したビット長以下で表せられる乱数生成
	 * @param {number} bits - Required number of bits (up to 64 possible).
	 * @returns {number}
	 */
	next(bits) {
		if(bits === 0) {
			return 0;
		}
		else if(bits === 32) {
			return this.genrand_int32();
		}
		else if(bits < 32) {
			// 線形合同法ではないため
			// 上位のビットを使用しなくてもいいがJavaっぽく。
			return this.genrand_int32() >>> (32 - bits);
		}
		// double型のため、52ビットまでは、整数として出力可能
		else if(bits === 63) {
			// 正の値を出力するように調節
			return this.next(32) * 0x80000000 + this.next(32);
		}
		else if(bits === 64) {
			return this.next(32) * 0x100000000 + this.next(32);
		}
		else if(bits < 64) {
			return this.genrand_int32() * (1 << (bits - 32)) + (this.genrand_int32()  >>> (64 - bits));
		}
	}

	/**
	 * 8ビット長整数の乱数の配列
	 * @param {number} size - 必要な長さ
	 * @returns {Array<number>}
	 */
	nextBytes(size) {
		const y = new Array(size);
		// 配列yに乱数を入れる
		// 8ビットのために、32ビット乱数を1回回すのはもったいない
		for(let i = 0;i < y.length; i++) {
			y[i] = this.next(8);
		}
		return y;
	}

	/**
	 * 16ビット長整数の乱数
	 * @returns {number}
	 */
	nextShort() {
		return this.next(16);
	}

	/**
	 * 32ビット長整数の乱数
	 * @param {number} [x] - 指定した値未満の数値を作る
	 * @returns {number}
	 */
	nextInt(x) {
		if((x !== undefined) && (typeof x === "number")) {
			let r, y;
			do {
				r = this.genrand_int32() >>> 0;
				y = r % x;
			} while((r - y + x) > 0x100000000 );
			return y;
		}
		return this.next(32) | 0;
	}

	/**
	 * 64ビット長整数の乱数
	 * @returns {number}
	 */
	nextLong() {
		return this.next(64);
	}

	/**
	 * bool値の乱数
	 * @returns {boolean}
	 */
	nextBoolean() {
		// 1ビットのために、32ビット乱数を1回回すのはもったいない
		return (this.next(1) !== 0);
	}

	/**
	 * float精度の実数
	 * @returns {number}
	 */
	nextFloat() {
		return (this.next(24) / 0x1000000);
	}

	/**
	 * double精度の実数
	 * @returns {number}
	 */
	nextDouble() {
		const a1 = this.next(26) * 0x8000000 + this.next(27);
		const a2 = 0x8000000 * 0x4000000;
		return (a1 / a2);
	}

	/**
	 * ガウシアン分布に従う乱数
	 * @returns {number}
	 */
	nextGaussian() {
		if(this.haveNextNextGaussian) {
			this.haveNextNextGaussian = false;
			return this.nextNextGaussian;
		}
		// Box-Muller法
		const a = Math.sqrt( -2 * Math.log( this.nextDouble() ) );
		const b = 2 * Math.PI * this.nextDouble();
		const y = a * Math.sin(b);
		this.nextNextGaussian = a * Math.cos(b);
		this.haveNextNextGaussian = true;
		return y;
	}

}

/**
 * 乱数生成用の初期シード値
 * @type {number}
 * @private
 * @ignore
 */
Random.seedUniquifier = 0x87654321;