├── .gitignore
├── README.md
├── UNLICENSE
├── bower.json
├── index.js
├── package.json
├── rng.js
└── test
├── browserify.js
└── cjs.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript seedable random number generator
2 |
3 | The generator below, seeded with "`Example`" will produce the same
4 | values as below for each of these random number distributions across
5 | all browsers.
6 |
7 | ```javascript
8 | var rng = new RNG('Example');
9 | rng.random(40, 50); // => 42
10 | rng.uniform(); // => 0.7972798995050903
11 | rng.normal(); // => -0.6698504543216376
12 | rng.exponential(); // => 1.0547367609131555
13 | rng.poisson(4); // => 2
14 | rng.gamma(4); // => 2.781724687386858
15 | ```
16 |
17 | The underlying algorithm is RC4 and uniform number generation is about
18 | 10x slower than `Math.random` in V8. What you get in exchange for that
19 | is a seedable generator and additional random distributions (see
20 | example). You can still get speed *and* these additional distributions
21 | by using Math.random as the core uniform number generator.
22 |
23 | ```javascript
24 | var rng = new RNG(Math.random);
25 | ```
26 |
27 | When no seed is provided, one is created randomly from available
28 | entropy sources. Seeds that are not strings are run through
29 | JSON.stringify() before being used.
30 |
31 | Here's how you would replace `Math.random` with a seeded generator.
32 |
33 | ```javascript
34 | Math.random = RNG.prototype.uniform.bind(new RNG('my seed'));
35 | ```
36 |
37 | Finally, for fun, a dice roller,
38 |
39 | ```javascript
40 | var dice = RNG.roller('4d6 + 10');
41 | dice(); // => 17
42 | dice(); // => 11
43 | ```
44 |
45 | ## Node.js Usage
46 |
47 | ```javascript
48 | var RNG = require('rng-js');
49 | ```
50 |
51 | This module can also be [browserified][browserify] thanks to
52 | [browserify-shim][shim].
53 |
54 |
55 | [browserify]: https://github.com/substack/node-browserify
56 | [shim]: https://github.com/thlorenz/browserify-shim
57 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rng-js",
3 | "version": "1.0.1",
4 | "main": "rng.js",
5 | "description": "Seedable random number generator functions.",
6 | "license": "Public Domain"
7 | }
8 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var vm = require('vm');
2 | var fs = require('fs');
3 | var path = require('path');
4 |
5 | // You shouldn't need to specify the whole path, but tap doesn't like a
6 | // relative path when testing.
7 | var localPath = path.join(__dirname, 'rng.js');
8 | var src = fs.readFileSync(localPath, { encoding: 'utf8' });
9 |
10 | var ctx = { window: {} };
11 | vm.runInNewContext(src, ctx);
12 |
13 | module.exports = ctx.RNG;
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rng-js",
3 | "version": "1.0.1",
4 | "description": "Seedable random number generator functions.",
5 | "browser": "rng.js",
6 | "main": "index.js",
7 | "scripts": {
8 | "test": "tap test/*.js"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git://github.com/skeeto/rng-js.git"
13 | },
14 | "keywords": [
15 | "random",
16 | "seed",
17 | "poisson",
18 | "gamma",
19 | "rc4",
20 | "exponential"
21 | ],
22 | "author": "Christopher Wellons ",
23 | "license": "Unlicence",
24 | "bugs": {
25 | "url": "https://github.com/skeeto/rng-js/issues"
26 | },
27 | "homepage": "https://github.com/skeeto/rng-js",
28 | "devDependencies": {
29 | "browserify": "~3.20.0",
30 | "browserify-shim": "~3.1.5",
31 | "tap": "~0.4.8"
32 | },
33 | "browserify": {
34 | "transform": [
35 | "browserify-shim"
36 | ]
37 | },
38 | "browserify-shim": {
39 | "./rng.js": "RNG"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/rng.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Seedable random number generator functions.
3 | * @version 1.0.0
4 | * @license Public Domain
5 | *
6 | * @example
7 | * var rng = new RNG('Example');
8 | * rng.random(40, 50); // => 42
9 | * rng.uniform(); // => 0.7972798995050903
10 | * rng.normal(); // => -0.6698504543216376
11 | * rng.exponential(); // => 1.0547367609131555
12 | * rng.poisson(4); // => 2
13 | * rng.gamma(4); // => 2.781724687386858
14 | */
15 |
16 | /**
17 | * @param {String} seed A string to seed the generator.
18 | * @constructor
19 | */
20 | function RC4(seed) {
21 | this.s = new Array(256);
22 | this.i = 0;
23 | this.j = 0;
24 | for (var i = 0; i < 256; i++) {
25 | this.s[i] = i;
26 | }
27 | if (seed) {
28 | this.mix(seed);
29 | }
30 | }
31 |
32 | /**
33 | * Get the underlying bytes of a string.
34 | * @param {string} string
35 | * @returns {Array} An array of bytes
36 | */
37 | RC4.getStringBytes = function(string) {
38 | var output = [];
39 | for (var i = 0; i < string.length; i++) {
40 | var c = string.charCodeAt(i);
41 | var bytes = [];
42 | do {
43 | bytes.push(c & 0xFF);
44 | c = c >> 8;
45 | } while (c > 0);
46 | output = output.concat(bytes.reverse());
47 | }
48 | return output;
49 | };
50 |
51 | RC4.prototype._swap = function(i, j) {
52 | var tmp = this.s[i];
53 | this.s[i] = this.s[j];
54 | this.s[j] = tmp;
55 | };
56 |
57 | /**
58 | * Mix additional entropy into this generator.
59 | * @param {String} seed
60 | */
61 | RC4.prototype.mix = function(seed) {
62 | var input = RC4.getStringBytes(seed);
63 | var j = 0;
64 | for (var i = 0; i < this.s.length; i++) {
65 | j += this.s[i] + input[i % input.length];
66 | j %= 256;
67 | this._swap(i, j);
68 | }
69 | };
70 |
71 | /**
72 | * @returns {number} The next byte of output from the generator.
73 | */
74 | RC4.prototype.next = function() {
75 | this.i = (this.i + 1) % 256;
76 | this.j = (this.j + this.s[this.i]) % 256;
77 | this._swap(this.i, this.j);
78 | return this.s[(this.s[this.i] + this.s[this.j]) % 256];
79 | };
80 |
81 | /**
82 | * Create a new random number generator with optional seed. If the
83 | * provided seed is a function (i.e. Math.random) it will be used as
84 | * the uniform number generator.
85 | * @param seed An arbitrary object used to seed the generator.
86 | * @constructor
87 | */
88 | function RNG(seed) {
89 | if (seed == null) {
90 | seed = '' + Math.random() + Date.now();
91 | } else if (typeof seed === "function") {
92 | // Use it as a uniform number generator
93 | this.uniform = seed;
94 | this.nextByte = function() {
95 | return ~~(this.uniform() * 256);
96 | };
97 | seed = null;
98 | } else if (Object.prototype.toString.call(seed) !== "[object String]") {
99 | seed = JSON.stringify(seed);
100 | }
101 | this._normal = null;
102 | if (seed) {
103 | this._state = new RC4(seed);
104 | } else {
105 | this._state = null;
106 | }
107 | }
108 |
109 | /**
110 | * @returns {number} Uniform random number between 0 and 255.
111 | */
112 | RNG.prototype.nextByte = function() {
113 | return this._state.next();
114 | };
115 |
116 | /**
117 | * @returns {number} Uniform random number between 0 and 1.
118 | */
119 | RNG.prototype.uniform = function() {
120 | var BYTES = 7; // 56 bits to make a 53-bit double
121 | var output = 0;
122 | for (var i = 0; i < BYTES; i++) {
123 | output *= 256;
124 | output += this.nextByte();
125 | }
126 | return output / (Math.pow(2, BYTES * 8) - 1);
127 | };
128 |
129 | /**
130 | * Produce a random integer within [n, m).
131 | * @param {number} [n=0]
132 | * @param {number} m
133 | *
134 | */
135 | RNG.prototype.random = function(n, m) {
136 | if (n == null) {
137 | return this.uniform();
138 | } else if (m == null) {
139 | m = n;
140 | n = 0;
141 | }
142 | return n + Math.floor(this.uniform() * (m - n));
143 | };
144 |
145 | /**
146 | * Generates numbers using this.uniform() with the Box-Muller transform.
147 | * @returns {number} Normally-distributed random number of mean 0, variance 1.
148 | */
149 | RNG.prototype.normal = function() {
150 | if (this._normal !== null) {
151 | var n = this._normal;
152 | this._normal = null;
153 | return n;
154 | } else {
155 | var x = this.uniform() || Math.pow(2, -53); // can't be exactly 0
156 | var y = this.uniform();
157 | this._normal = Math.sqrt(-2 * Math.log(x)) * Math.sin(2 * Math.PI * y);
158 | return Math.sqrt(-2 * Math.log(x)) * Math.cos(2 * Math.PI * y);
159 | }
160 | };
161 |
162 | /**
163 | * Generates numbers using this.uniform().
164 | * @returns {number} Number from the exponential distribution, lambda = 1.
165 | */
166 | RNG.prototype.exponential = function() {
167 | return -Math.log(this.uniform() || Math.pow(2, -53));
168 | };
169 |
170 | /**
171 | * Generates numbers using this.uniform() and Knuth's method.
172 | * @param {number} [mean=1]
173 | * @returns {number} Number from the Poisson distribution.
174 | */
175 | RNG.prototype.poisson = function(mean) {
176 | var L = Math.exp(-(mean || 1));
177 | var k = 0, p = 1;
178 | do {
179 | k++;
180 | p *= this.uniform();
181 | } while (p > L);
182 | return k - 1;
183 | };
184 |
185 | /**
186 | * Generates numbers using this.uniform(), this.normal(),
187 | * this.exponential(), and the Marsaglia-Tsang method.
188 | * @param {number} a
189 | * @returns {number} Number from the gamma distribution.
190 | */
191 | RNG.prototype.gamma = function(a) {
192 | var d = (a < 1 ? 1 + a : a) - 1 / 3;
193 | var c = 1 / Math.sqrt(9 * d);
194 | do {
195 | do {
196 | var x = this.normal();
197 | var v = Math.pow(c * x + 1, 3);
198 | } while (v <= 0);
199 | var u = this.uniform();
200 | var x2 = Math.pow(x, 2);
201 | } while (u >= 1 - 0.0331 * x2 * x2 &&
202 | Math.log(u) >= 0.5 * x2 + d * (1 - v + Math.log(v)));
203 | if (a < 1) {
204 | return d * v * Math.exp(this.exponential() / -a);
205 | } else {
206 | return d * v;
207 | }
208 | };
209 |
210 | /**
211 | * Accepts a dice rolling notation string and returns a generator
212 | * function for that distribution. The parser is quite flexible.
213 | * @param {string} expr A dice-rolling, expression i.e. '2d6+10'.
214 | * @param {RNG} rng An optional RNG object.
215 | * @returns {Function}
216 | */
217 | RNG.roller = function(expr, rng) {
218 | var parts = expr.split(/(\d+)?d(\d+)([+-]\d+)?/).slice(1);
219 | var dice = parseFloat(parts[0]) || 1;
220 | var sides = parseFloat(parts[1]);
221 | var mod = parseFloat(parts[2]) || 0;
222 | rng = rng || new RNG();
223 | return function() {
224 | var total = dice + mod;
225 | for (var i = 0; i < dice; i++) {
226 | total += rng.random(sides);
227 | }
228 | return total;
229 | };
230 | };
231 |
232 | /* Provide a pre-made generator instance. */
233 | RNG.$ = new RNG();
234 |
--------------------------------------------------------------------------------
/test/browserify.js:
--------------------------------------------------------------------------------
1 | var browserify = require('browserify'),
2 | test = require('tap').test,
3 | vm = require('vm');
4 |
5 | // This code is taken nearly verbatim from browserify-shim's tests:
6 | // https://github.com/thlorenz/browserify-shim/blob/master/test/bundle-deps.js
7 |
8 | test('Bundle via browserify works', function(t) {
9 |
10 | var relPath = '../';
11 |
12 | browserify( { ignoreGlobals: true })
13 | .require(require.resolve(relPath))
14 | .bundle(function (err, src) {
15 |
16 | if (err) { t.fail(err); return t.end(); }
17 |
18 | var ctx = { window: {}, console: console };
19 | ctx.self = ctx.window;
20 | var require_ = vm.runInNewContext(src, ctx);
21 |
22 | var RNG = require_(require.resolve(relPath));
23 |
24 | t.ok(RNG, 'RNG is defined');
25 | t.ok(RNG.$, 'Prebuilt instance comes along');
26 | t.ok(RNG.roller, 'Roller function is present');
27 | t.end();
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/test/cjs.js:
--------------------------------------------------------------------------------
1 | var test = require('tap').test;
2 |
3 | test('Loading via CJS (node) works', function(t) {
4 |
5 | var RNG = require('../');
6 |
7 | t.ok(RNG, 'RNG is defined');
8 | t.ok(RNG.$, 'Prebuilt instance comes along');
9 | t.ok(RNG.roller, 'Roller function is present');
10 | t.end();
11 | });
12 |
--------------------------------------------------------------------------------