@environment-safe/random
Version:
seed based multi type random number generation
200 lines (185 loc) • 5.76 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.resetGlobal = exports.Random = void 0;
//seed random is not import compatible, but we still need to use it
//import { createRequire } from "module";
//const require = createRequire(import.meta.url);
//const rand = require("seed-random");
//'use strict';
//*
var width = 256; // each RC4 output is 0 <= x < 256
var chunks = 6; // at least six RC4 outputs for each double
var digits = 52; // there are 52 significant digits in a double
var pool = []; // pool: entropy pool starts empty
//var GLOBAL = typeof global === 'undefined' ? window : global;
//
// The following constants are related to IEEE 754 limits.
//
var startdenom = Math.pow(width, chunks),
significance = Math.pow(2, digits),
overflow = significance * 2,
mask = width - 1;
var oldRandom = Math.random;
const rand = function (seed, options) {
if (options && options.global === true) {
options.global = false;
Math.random = module.exports(seed, options);
options.global = true;
return Math.random;
}
var use_entropy = options && options.entropy || false;
var key = [];
// Flatten the seed string or build one from local entropy if needed.
//*
//var shortseed =
mixkey(flatten(use_entropy ? [seed, tostring(pool)] : 0 in arguments ? seed : autoseed(), 3), key); //*/
// Use the seed to initialize an ARC4 generator.
var arc4 = new ARC4(key);
// Mix the randomness into accumulated entropy.
mixkey(tostring(arc4.S), pool);
// Override Math.random
// This function returns a random double in [0, 1) that contains
// randomness in every bit of the mantissa of the IEEE 754 value.
return function () {
// Closure to return a random double:
var n = arc4.g(chunks),
// Start with a numerator n < 2 ^ 48
d = startdenom,
// and denominator d = 2 ^ 48.
x = 0; // and no 'extra last byte'.
while (n < significance) {
// Fill up all significant digits by
n = (n + x) * width; // shifting numerator and
d *= width; // denominator and generating a
x = arc4.g(1); // new least-significant-byte.
}
while (n >= overflow) {
// To avoid rounding up, before adding
n /= 2; // last byte, shift everything
d /= 2; // right using integer Math until
x >>>= 1; // we have exactly the desired bits.
}
return (n + x) / d; // Form the number within [0, 1).
};
};
const resetGlobal = function () {
Math.random = oldRandom;
};
exports.resetGlobal = resetGlobal;
function ARC4(key) {
var t,
keylen = key.length,
me = this,
i = 0,
j = me.i = me.j = 0,
s = me.S = [];
// The empty key [] is treated as [0].
if (!keylen) {
key = [keylen++];
}
// Set up S using the standard key scheduling algorithm.
while (i < width) {
s[i] = i++;
}
for (i = 0; i < width; i++) {
s[i] = s[j = mask & j + key[i % keylen] + (t = s[i])];
s[j] = t;
}
// The "g" method returns the next (count) outputs as one number.
(me.g = function (count) {
// Using instance members instead of closure state nearly doubles speed.
var t,
r = 0,
i = me.i,
j = me.j,
s = me.S;
while (count--) {
t = s[i = mask & i + 1];
r = r * width + s[mask & (s[i] = s[j = mask & j + t]) + (s[j] = t)];
}
me.i = i;
me.j = j;
return r;
// For robust unpredictability discard an initial batch of values.
// See http://www.rsa.com/rsalabs/node.asp?id=2009
})(width);
}
function flatten(obj, depth) {
var result = [],
typ = (typeof obj)[0],
prop;
if (depth && typ == 'o') {
for (prop in obj) {
try {
result.push(flatten(obj[prop], depth - 1));
} catch (e) {
//noop
}
}
}
return result.length ? result : typ == 's' ? obj : obj + '\0';
}
function mixkey(seed, key) {
var stringseed = seed + '',
smear,
j = 0;
while (j < stringseed.length) {
key[mask & j] = mask & (smear ^= key[mask & j] * 19) + stringseed.charCodeAt(j++);
}
return tostring(key);
}
function autoseed(seed) {
try {
globalThis.crypto.getRandomValues(seed = new Uint8Array(width));
return tostring(seed);
} catch (e) {
return [+new Date(), globalThis, globalThis.navigator && globalThis.navigator.plugins, globalThis.screen, tostring(pool)];
}
}
function tostring(a) {
return String.fromCharCode.apply(0, a);
}
//
// When seedrandom.js is loaded, we immediately mix a few bits
// from the built-in RNG into the entropy pool. Because we do
// not want to intefere with determinstic PRNG state later,
// seedrandom will not call Math.random on its own again after
// initialization.
//
mixkey(Math.random(), pool);
//*/
const defaultAlphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
class Random {
constructor(options = {}) {
this.options = options;
this.rnd = rand(options.seed || Date.now().toString() + Math.random());
}
ratio() {
return this.rnd();
}
random() {
return this.rnd();
}
float(upperBound = Number.MAX_VALUE, lowerBound = 0) {
const delta = upperBound - lowerBound;
return lowerBound + delta * this.rnd();
}
integer(upperBound = Math.floor(Number.MAX_VALUE), lowerBound = 0) {
return Math.floor(this.float(upperBound, lowerBound));
}
array(array) {
return array[this.integer(array.length)];
}
string(parts = defaultAlphabet, max = 10, min = 0) {
const numParts = this.integer(max, min);
let lcv = 0;
let result = '';
for (; lcv < numParts; lcv++) {
result += this.array(parts);
}
return result;
}
}
exports.Random = Random;
;