Home Reference Source

src/math/core/tools/Random.js

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

import Polyfill from "../../tools/Polyfill.js";
import MaximumLengthSequence from "./random/MaximumLengthSequence.js";
import Xorshift from "./random/Xorshift.js";
import RandomBase from "./random/RandomBase.js";

/**
 * Setting random numbers
 * @typedef {Object} KRandomSettings
 * @property {number} [seed] Seed number for random number generation. If not specified, create from time.
 * @property {string} [algorithm="FAST"] Algorithm type : "XORSHIFT" / "MLS" / "FAST"
 */

/**
 * Random number class.
 */
export default class Random {
	
	/**
	 * Create Random.
	 * - algorithm : "XORSHIFT" / "MLS" / "FAST"
	 * @param {number|KRandomSettings} [init_data] - Seed number for random number generation. If not specified, create from time.
	 */
	constructor(init_data) {
		let seed_number = undefined;
		let algorithm = "fast";

		/**
		 * Random Number Generator.
		 * @private
		 * @type {RandomBase}
		 */
		this.rand = null;
		
		/**
		 * have `NextNextGaussian`
		 * @private
		 * @type {boolean}
		 */
		this.haveNextNextGaussian = false;

		/**
		 * Normally distributed random numbers.
		 * @private
		 * @type {number}
		 */
		this.nextNextGaussian = 0.0;

		if(typeof init_data === "number") {
			seed_number = init_data;
		}
		else if(typeof init_data === "object") {
			if(init_data.seed !== undefined) {
				seed_number = init_data.seed;
			}
			if(init_data.algorithm !== undefined) {
				algorithm = init_data.algorithm;
			}
		}
		if(/fast|xorshift/i.test(algorithm)) {
			// XORSHIFT
			this.rand = new Xorshift(seed_number);
		}
		else {
			// MLS
			this.rand = new MaximumLengthSequence(seed_number);
		}
	}

	/**
	 * Create Random.
	 * - algorithm : "XORSHIFT" / "MLS" / "FAST"
	 * @param {number|KRandomSettings} [init_data] - Seed number for random number generation. If not specified, create from time.
	 */
	static create(init_data) {
		return new Random(init_data);
	}

	/**
	 * Initialize random seed.
	 * @param {number} seed
	 */
	setSeed(seed) {
		this.rand.setSeed(seed);
	}

	/**
	 * 32-bit random number.
	 * @returns {number} - 32-bit random number
	 * @private
	 * @ignore
	 */
	genrand_int32() {
		return this.rand.genrand_int32();
	}

	/**
	 * Random number of specified bit length.
	 * @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.genrand_int32() * 0x80000000 + this.genrand_int32());
		}
		else if(bits === 64) {
			return (this.genrand_int32() * 0x100000000 + this.genrand_int32());
		}
		else if(bits < 63) {
			return (this.genrand_int32() * (1 << (bits - 32)) + (this.genrand_int32()  >>> (64 - bits)));
		}
	}

	/**
	 * 8-bit random number array of specified length.
	 * @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;
	}
	
	/**
	 * 32-bit random number.
	 * @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) & 0xFFFFFFFF);
	}

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

	/**
	 * Double type random number in the range of [0, 1).
	 * @returns {number}
	 */
	nextDouble() {
		const a1 = this.next(26) * 0x8000000 + this.next(27);
		const a2 = 0x8000000 * 0x4000000;
		return (a1 / a2);
	}

	/**
	 * Random numbers from a Gaussian distribution.
	 * This random number is a distribution with an average value of 0 and a standard deviation of 1.
	 * @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;
	}
}