Source: MersenneTwister.js

/**
 * xethya-random-mtw
 *
 * Copyright © 2016 Joel A. Villarreal Bertoldi. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

/**
 * @ignore
 */
const N = 624;

/**
 * @ignore
 */
const M = 397;

/**
 * @ignore
 */
const MATRIX_A = 0x9908b0df;

/**
 * @ignore
 */
const UPPER_MASK = 0x80000000;

/**
 * @ignore
 */
const LOWER_MASK = 0x7fffffff;

/**
 * @ignore
 */
const INIT_BY_ARRAY_SEED = 19650218;

/**
 * Implementation of the Mersenne-Twister algorithm for generation of
 * pseudo-random numbers.
 *
 * @see https://gist.github.com/banksean/300494
 */
class MersenneTwister {

  /**
   * Instantiates the Mersenne-Twister generator.
   *
   * @param  {Number} seed - Seed value, optional.
   * @constructor
   */
  constructor(seed = null) {
    let seedNumber;

    if (seed) {
      seedNumber = Math.abs(seed);
    } else {
      if (typeof process !== 'undefined') {
        // Try seeding with Node's process.hrtime.
        seedNumber = Number(process.hrtime().join(''));
      } else if (typeof window.performance !== 'undefined') {
        // Try seeding with window.performance.
        seedNumber = window.performance.now();
      } else {
        // Try seeding with a custom algorithm.
        seedNumber = Number(new Date().getTime().toString().split('')
          .sort(() => 0.5 - Math.random()).join(''));
      }
    }

    this.MT = new Array(N);
    this.MTI = N + 1;

    this.initializeRandomGenerator(seedNumber);
  }

  /**
   * Determines if the generator works better by being reinstantiated after
   * every generated number.
   *
   * @return {Boolean}
   * @static
   */
  static recommendsToReinstantiate() {
    return false;
  }

  /**
   * Loads the initialization vector required for the algorithm,
   * according to a given seed.
   *
   * @param  {Number} seedNumber - A seed can be any non-negative integer value.
   * @function
   */
  initializeRandomGenerator(seedNumber) {
    if (!seedNumber) {
      throw new Error('MersenneTwister#initializeRandomGenerator: A seed number is required.');
    }

    let seed = Math.abs(Math.floor(seedNumber));

    this.MT[0] = seed >> 0;
    for (this.MTI = 1; this.MTI < N; this.MTI += 1) {
      seed = this.MT[this.MTI - 1] ^ (this.MT[this.MTI - 1] >> 30);
      this.MT[this.MTI] = ((((seed & 0xffff0000) >> 16) * 1812433253) << 16)
                        + ((seed & 0x0000ffff) * 1812433253)
                        + this.MTI;
      this.MT[this.MTI] = this.MT[this.MTI] >> 0;
    }
  }

  /**
   * An alternative way to load the initialization vector for the algorithm.
   *
   * @param  {Array.<Number>} initKeyArray - A list of non-negative integer values.
   * @function
   */
  initializeByArray(initKeyArray) {
    let i = 1;
    let j = 0;
    const keyLength = initKeyArray.length;

    if (keyLength === 0) {
      throw new Error('MersenneTwister#initializeByArray: initKeyArray must be '
        + 'an Array of at least one non-negative number.');
    }

    // Ensure positive, integer values.
    const initKey = initKeyArray.map(v => Math.abs(Math.floor(v)));

    this.initializeRandomGenerator(INIT_BY_ARRAY_SEED);

    let k = N > keyLength ? N : keyLength;

    while (k > 0) {
      const s = this.MT[i - 1] ^ (this.MT[i - 1] >> 30);
      this.MT[i] = (this.MT[i] ^ (((((s & 0xffff0000) >> 16) * 1664525) << 16)
        + ((s & 0x0000ffff) * 1664525)))
        + initKey[j] + j;
      this.MT[i] = this.MT[i] >> 0;
      i += 1;
      j += 1;
      if (i >= N) {
        this.MT[0] = this.MT[N - 1];
        i = 1;
      }
      if (j >= keyLength) {
        j = 0;
      }
      k -= 1;
    }
    for (k = N - 1; k > 0; k -= 1) {
      const s = this.MT[i - 1] ^ (this.MT[i - 1] >> 30);
      this.MT[i] = (this.MT[i] ^ (((((s & 0xffff0000) >> 16) * 1566083941) << 16)
        + ((s & 0x0000ffff) * 1566083941))) - i;
      this.MT[i] = this.MT[i] >> 0;
      i += 1;
      if (i >= N) {
        this.MT[0] = this.MT[N - 1];
        i = 1;
      }
    }
    this.MT[0] = 0x80000000;
  }

  /**
   * Returns a random non-negative integer value.
   *
   * @function
   * @return {Number}
   */
  generateRandomInteger() {
    let y;
    const mag01 = [0x0, MATRIX_A];

    if (this.MTI >= N) {
      let kk;
      if (this.MTI === N + 1) {
        this.initializeRandomGenerator(5489);
      }
      for (kk = 0; kk < N - M; kk += 1) {
        y = (this.MT[kk] & UPPER_MASK) | (this.MT[kk + 1] & LOWER_MASK);
        this.MT[kk] = this.MT[kk + M] ^ (y >> 1) ^ mag01[y & 0x1];
      }
      while (kk < N - 1) {
        y = (this.MT[kk] & UPPER_MASK) | (this.MT[kk + 1] & LOWER_MASK);
        this.MT[kk] = this.MT[kk + M - N] ^ (y >> 1) ^ mag01[y & 0x1];
        kk += 1;
      }
      y = (this.MT[N - 1] & UPPER_MASK) | (this.MT[0] & LOWER_MASK);
      this.MT[N - 1] = this.MT[M - 1] ^ (y >> 1) ^ mag01[y & 0x1];

      this.MTI = 0;
    }

    this.MTI += 1;
    y = this.MT[this.MTI];

    y ^= (y >> 11);
    y ^= (y << 7) & 0x9d2c5680;
    y ^= (y << 15) & 0xefc60000;
    y ^= (y >> 18);

    return y >> 0;
  }

  /**
   * Returns a non-negative random integer value, within
   * the range of Int31.
   *
   * @function
   * @return {Number}
   */
  generateRandomInteger31() {
    return this.generateRandomInteger() >> 1;
  }

  /**
   * Returns a non-negative random real number between 0 and 1.
   *
   * @function
   * @return {Number}
   */
  generateRandomReal() {
    return this.generateRandomInteger() * (1.0 / 4294967295.0);
  }

  /**
   * Returns a non-negative random number between 0 and 1.
   *
   * @function
   * @return {Number}
   */
  generateRandom() {
    return this.generateRandomInteger() * (1.0 / 4294967296.0);
  }

  /**
   * Returns a non-negative random real number between 0 and 1.
   *
   * @function
   * @return {Number}
   */
  generateRandomReal3() {
    return (this.generateRandomInteger() + 0.5) * (1.0 / 4294967296.0);
  }

  /**
   * Returns a non-negative random rumber with a resolution
   * of 53 bits.
   *
   * @function
   * @return {Number}
   */
  generateRandomReal53BitResolution() {
    const a = this.generateRandomInteger() >> 5;
    const b = this.generateRandomInteger() >> 6;
    return (a * 671084464.0 + b) * (1.0 / 9007199254740992.0);
  }
}

export default MersenneTwister;