blob: fb57a3ac2fb018197d7f4bf201059b9bc31485f4 [file] [log] [blame]
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview PseudoRandom provides a mechanism for generating deterministic
* psuedo random numbers based on a seed. Based on the Park-Miller algorithm.
* See http://dx.doi.org/10.1145%2F63039.63042 for details.
*
*/
goog.provide('goog.testing.PseudoRandom');
goog.require('goog.Disposable');
/**
* Class for unit testing code that uses Math.random. Generates deterministic
* random numbers.
*
* @param {number=} opt_seed The seed to use.
* @param {boolean=} opt_install Whether to install the PseudoRandom at
* construction time.
* @extends {goog.Disposable}
* @constructor
* @final
*/
goog.testing.PseudoRandom = function(opt_seed, opt_install) {
goog.Disposable.call(this);
if (!goog.isDef(opt_seed)) {
opt_seed = goog.testing.PseudoRandom.seedUniquifier_++ + goog.now();
}
this.seed(opt_seed);
if (opt_install) {
this.install();
}
};
goog.inherits(goog.testing.PseudoRandom, goog.Disposable);
/**
* Helps create a unique seed.
* @type {number}
* @private
*/
goog.testing.PseudoRandom.seedUniquifier_ = 0;
/**
* Constant used as part of the algorithm.
* @type {number}
*/
goog.testing.PseudoRandom.A = 48271;
/**
* Constant used as part of the algorithm. 2^31 - 1.
* @type {number}
*/
goog.testing.PseudoRandom.M = 2147483647;
/**
* Constant used as part of the algorithm. It is equal to M / A.
* @type {number}
*/
goog.testing.PseudoRandom.Q = 44488;
/**
* Constant used as part of the algorithm. It is equal to M % A.
* @type {number}
*/
goog.testing.PseudoRandom.R = 3399;
/**
* Constant used as part of the algorithm to get values from range [0, 1).
* @type {number}
*/
goog.testing.PseudoRandom.ONE_OVER_M_MINUS_ONE =
1.0 / (goog.testing.PseudoRandom.M - 1);
/**
* The seed of the random sequence and also the next returned value (before
* normalization). Must be between 1 and M - 1 (inclusive).
* @type {number}
* @private
*/
goog.testing.PseudoRandom.prototype.seed_ = 1;
/**
* Whether this PseudoRandom has been installed.
* @type {boolean}
* @private
*/
goog.testing.PseudoRandom.prototype.installed_;
/**
* The original Math.random function.
* @type {function(): number}
* @private
*/
goog.testing.PseudoRandom.prototype.mathRandom_;
/**
* Installs this PseudoRandom as the system number generator.
*/
goog.testing.PseudoRandom.prototype.install = function() {
if (!this.installed_) {
this.mathRandom_ = Math.random;
Math.random = goog.bind(this.random, this);
this.installed_ = true;
}
};
/** @override */
goog.testing.PseudoRandom.prototype.disposeInternal = function() {
goog.testing.PseudoRandom.superClass_.disposeInternal.call(this);
this.uninstall();
};
/**
* Uninstalls the PseudoRandom.
*/
goog.testing.PseudoRandom.prototype.uninstall = function() {
if (this.installed_) {
Math.random = this.mathRandom_;
this.installed_ = false;
}
};
/**
* Seed the generator.
*
* @param {number=} seed The seed to use.
*/
goog.testing.PseudoRandom.prototype.seed = function(seed) {
this.seed_ = seed % (goog.testing.PseudoRandom.M - 1);
if (this.seed_ <= 0) {
this.seed_ += goog.testing.PseudoRandom.M - 1;
}
};
/**
* @return {number} The next number in the sequence.
*/
goog.testing.PseudoRandom.prototype.random = function() {
var hi = Math.floor(this.seed_ / goog.testing.PseudoRandom.Q);
var lo = this.seed_ % goog.testing.PseudoRandom.Q;
var test = goog.testing.PseudoRandom.A * lo -
goog.testing.PseudoRandom.R * hi;
if (test > 0) {
this.seed_ = test;
} else {
this.seed_ = test + goog.testing.PseudoRandom.M;
}
return (this.seed_ - 1) * goog.testing.PseudoRandom.ONE_OVER_M_MINUS_ONE;
};