UNPKG

14.3 kBMarkdownView Raw
1# Random.js
2
3[![Build Status](https://travis-ci.org/ckknight/random-js.svg?branch=master)](https://travis-ci.org/ckknight/random-js)
4
5This is designed to be a mathematically correct random number generator library for JavaScript.
6
7Inspiration was primarily taken from C++11's `<random>`.
8
9## Upgrading from 1.0
10Upgrading from 1.0 to 2.0 is a major, breaking change. For the most part, the way exports are defined is different. Instead of everything being available as static properties on a class-like function, random-js 2.0 exports each binding in accordance with current ECMAScript standards.
11
12## Why is this needed?
13
14Despite `Math.random()` being capable of producing numbers within [0, 1), there are a few downsides to doing so:
15
16- It is inconsistent between engines as to how many bits of randomness:
17 - Internet Explorer: 53 bits
18 - Mozilla Firefox: 53 bits
19 - Google Chrome/node.js: 32 bits
20 - Apple Safari: 32 bits
21- It is non-deterministic, which means you can't replay results consistently
22- In older browsers, there can be manipulation through cross-frame random polling. _This is mostly fixed in newer browsers and is required to be fixed in ECMAScript 6._
23
24Also, and most crucially, most developers tend to use improper and biased logic as to generating integers within a uniform distribution.
25
26## How does Random.js alleviate these problems?
27
28Random.js provides a set of "engines" for producing random integers, which consistently provide values within [0, 4294967295], i.e. 32 bits of randomness.
29
30- `nativeMath`: Utilizes [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) and converts its result to a signed integer. This is appropriate to use if you do not care for a deterministic implementation. Based on the implementation (which is hidden to you as a developer), the period may be shorter than expected and start repeating itself.
31- `browserCrypto`: Utilizes [`crypto.getRandomValues(Int32Array)`](https://developer.mozilla.org/en-US/docs/Web/API/window.crypto.getRandomValues). Only supported on newer browsers, but promises cryptographically random numbers.
32- `nodeCrypto`: Utilizes [`require('crypto').randomBytes(size)`](https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback). Only supported on node.
33- `MersenneTwister19937`: An implementation of the [Mersenne Twister](http://en.wikipedia.org/wiki/Mersenne_twister) algorithm. Not cryptographically secure, but its results are repeatable. Must be seeded with a single integer or an array of integers or call `.autoSeed()` to automatically seed initial data. Guaranteed to produce consistent results across all JavaScript implementations assuming the same seed.
34
35One is also free to implement their own engine as long as it returns 32-bit integers, either signed or unsigned.
36
37Some common, biased, _incorrect_ tool for generating random integers is as follows:
38
39 // DO NOT USE, BIASED LOGIC
40 function randomInt(min, max) {
41 return Math.floor(Math.random() * (max - min)) + min;
42 }
43 // DO NOT USE, BIASED LOGIC (typical C-like implementation)
44 function randomIntByModulo(min, max) {
45 var i = (Math.random() * 32768) >>> 0;
46 return (i % (min - max)) + min;
47 }
48
49The problem with both of these approaches is that the distribution of integers that it returns is not uniform. That is, it might be more biased to return `0` rather than `1`, making it inherently broken.
50
51`randomInt` may more evenly distribute its biased, but it is still wrong. `randomIntByModulo`, at least in the example given, is heavily biased to return [0, 67] over [68, 99].
52
53In order to eliminate bias, sometimes the engine which random data is pulled from may need to be used more than once.
54
55Random.js provides a series of distributions to alleviate this.
56
57## API
58
59### Engines
60
61- `nativeMath`: Utilizes `Math.random()`
62- `browserCrypto`: Utilizes `crypto.getRandomValues()`
63- `nodeCrypto`: Utilizes `require('crypto').randomBytes()`
64- `MersenneTwister19937`: Produces a new Mersenne Twister. Must be seeded before use.
65
66Or you can make your own!
67
68```ts
69interface Engine {
70 next(): number; // an int32
71}
72```
73
74Any object that fulfills that interface is an `Engine`.
75
76### Mersenne Twister API
77
78- `const mt = MersenneTwister19937.seed(value)`: Seed the twister with an initial 32-bit integer.
79- `const mt = MersenneTwister19937.seedWithArray(array)`: Seed the twister with an array of 32-bit integers.
80- `const mt = MersenneTwister19937.autoSeed()`: Seed the twister with automatic information. This uses the current Date and other entropy sources.
81- `mt.next()`: Produce a 32-bit signed integer.
82- `mt.discard(count)`: Discard `count` random values. More efficient than running `mt.next()` repeatedly.
83- `mt.getUseCount()`: Return the number of times the engine has been used plus the number of discarded values.
84
85One can seed a Mersenne Twister with the same value (`MersenneTwister19937.seed(value)`) or values (`MersenneTwister19937.seedWithArray(array)`) and discard the number of uses (`mt.getUseCount()`) to achieve the exact same state.
86
87If you wish to know the initial seed of `MersenneTwister19937.autoSeed()`, it is recommended to use the `createEntropy()` function to create the seed manually (this is what `autoSeed` does under-the-hood).
88
89```js
90const seed = createEntropy();
91const mt = MersenneTwister19937.seedWithArray(seed);
92useTwisterALot(mt); // you'll have to implement this yourself
93const clone = MersenneTwister19937.seedWithArray(seed).discard(
94 mt.getUseCount()
95);
96// at this point, `mt` and `clone` will produce equivalent values
97```
98
99### Distributions
100
101Random.js also provides a set of methods for producing useful data from an engine.
102
103- `integer(min, max)(engine)`: Produce an integer within the inclusive range [`min`, `max`]. `min` can be at its minimum -9007199254740992 (-2 ** 53). `max` can be at its maximum 9007199254740992 (2 ** 53).
104- `real(min, max, inclusive)(engine)`: Produce a floating point number within the range [`min`, `max`) or [`min`, `max`]. Uses 53 bits of randomness.
105- `bool()(engine)`: Produce a boolean with a 50% chance of it being `true`.
106- `bool(percentage)(engine)`: Produce a boolean with the specified chance causing it to be `true`.
107- `bool(numerator, denominator)(engine)`: Produce a boolean with `numerator`/`denominator` chance of it being true.
108- `pick(engine, array[, begin[, end]])`: Return a random value within the provided `array` within the sliced bounds of `begin` and `end`.
109- `picker(array[, begin[, end]])(engine)`: Same as `pick(engine, array, begin, end)`.
110- `shuffle(engine, array)`: Shuffle the provided `array` (in-place). Similar to `.sort()`.
111- `sample(engine, population, sampleSize)`: From the `population` array, produce an array with `sampleSize` elements that are randomly chosen without repeats.
112- `die(sideCount)(engine)`: Same as `integer(1, sideCount)(engine)`
113- `dice(sideCount, dieCount)(engine)`: Produce an array of length `dieCount` with as many `die` rolls.
114- `uuid4(engine)`: Produce a [Universally Unique Identifier](http://en.wikipedia.org/wiki/Universally_unique_identifier) Version 4.
115- `string()(engine, length)`: Produce a random string using numbers, uppercase and lowercase letters, `_`, and `-` of length `length`.
116- `string(pool)(engine, length)`: Produce a random string using the provided string `pool` as the possible characters to choose from of length `length`.
117- `hex()(engine, length)` or `hex(false)(engine, length)`: Produce a random string comprised of numbers or the characters `abcdef` of length `length`.
118- `hex(true)(engine, length)`: Produce a random string comprised of numbers or the characters `ABCDEF` of length `length`.
119- `date(start, end)(engine)`: Produce a random `Date` within the inclusive range of [`start`, `end`]. `start` and `end` must both be `Date`s.
120
121An example of using `integer` would be as such:
122
123```js
124// create a Mersenne Twister-19937 that is auto-seeded based on time and other random values
125const engine = MersenneTwister19937.autoSeed();
126// create a distribution that will consistently produce integers within inclusive range [0, 99].
127const distribution = integer(0, 99);
128// generate a number that is guaranteed to be within [0, 99] without any particular bias.
129function generateNaturalLessThan100() {
130 return distribution(engine);
131}
132```
133
134Producing a distribution should be considered a cheap operation, but producing a new Mersenne Twister can be expensive.
135
136An example of producing a random SHA1 hash:
137
138```js
139// using essentially Math.random()
140var engine = nativeMath;
141// lower-case Hex string distribution
142var distribution = hex(false);
143// generate a 40-character hex string
144function generateSHA1() {
145 return distribution(engine, 40);
146}
147```
148
149## Alternate API
150
151There is an alternate API which may be easier to use, but may be less performant. In scenarios where performance is paramount, it is recommended to use the aforementioned API.
152
153```js
154const random = new Random(
155 MersenneTwister19937.seedWithArray([0x12345678, 0x90abcdef])
156);
157const value = r.integer(0, 99);
158
159const otherRandom = new Random(); // same as new Random(nativeMath)
160```
161
162This abstracts the concepts of engines and distributions.
163
164- `r.integer(min, max)`: Produce an integer within the inclusive range [`min`, `max`]. `min` can be at its minimum -9007199254740992 (2 ** 53). `max` can be at its maximum 9007199254740992 (2 ** 53). The special number `-0` is never returned.
165- `r.real(min, max, inclusive)`: Produce a floating point number within the range [`min`, `max`) or [`min`, `max`]. Uses 53 bits of randomness.
166- `r.bool()`: Produce a boolean with a 50% chance of it being `true`.
167- `r.bool(percentage)`: Produce a boolean with the specified chance causing it to be `true`.
168- `r.bool(numerator, denominator)`: Produce a boolean with `numerator`/`denominator` chance of it being true.
169- `r.pick(array[, begin[, end]])`: Return a random value within the provided `array` within the sliced bounds of `begin` and `end`.
170- `r.shuffle(array)`: Shuffle the provided `array` (in-place). Similar to `.sort()`.
171- `r.sample(population, sampleSize)`: From the `population` array, produce an array with `sampleSize` elements that are randomly chosen without repeats.
172- `r.die(sideCount)`: Same as `r.integer(1, sideCount)`
173- `r.dice(sideCount, dieCount)`: Produce an array of length `dieCount` with as many `die` rolls.
174- `r.uuid4()`: Produce a [Universally Unique Identifier](http://en.wikipedia.org/wiki/Universally_unique_identifier) Version 4.
175- `r.string(length)`: Produce a random string using numbers, uppercase and lowercase letters, `_`, and `-` of length `length`.
176- `r.string(length, pool)`: Produce a random string using the provided string `pool` as the possible characters to choose from of length `length`.
177- `r.hex(length)` or `r.hex(length, false)`: Produce a random string comprised of numbers or the characters `abcdef` of length `length`.
178- `r.hex(length, true)`: Produce a random string comprised of numbers or the characters `ABCDEF` of length `length`.
179- `r.date(start, end)`: Produce a random `Date` within the inclusive range of [`start`, `end`]. `start` and `end` must both be `Date`s.
180
181## Usage
182
183### node.js
184
185In your project, run the following command:
186
187```sh
188npm install random-js
189```
190
191or
192
193```sh
194yarn add random-js
195```
196
197In your code:
198
199```js
200// ES6 Modules
201import { Random } from "random-js";
202const random = new Random(); // uses the nativeMath engine
203const value = random.integer(1, 100);
204```
205
206```js
207// CommonJS Modules
208const { Random } = require("random-js");
209const random = new Random(); // uses the nativeMath engine
210const value = random.integer(1, 100);
211```
212
213Or to have more control:
214
215```js
216const Random = require("random-js").Random;
217const random = new Random(MersenneTwister19937.autoSeed());
218const value = random.integer(1, 100);
219```
220
221It is recommended to create one shared engine and/or `Random` instance per-process rather than one per file.
222
223### Browser using AMD or RequireJS
224
225Download `random.min.js` and place it in your project, then use one of the following patterns:
226
227```js
228define(function(require) {
229 var Random = require("random");
230 return new Random.Random(Random.MersenneTwister19937.autoSeed());
231});
232
233define(function(require) {
234 var Random = require("random");
235 return new Random.Random();
236});
237
238define(["random"], function(Random) {
239 return new Random.Random(Random.MersenneTwister19937.autoSeed());
240});
241```
242
243### Browser using `<script>` tag
244
245Download `random-js.min.js` and place it in your project, then add it as a `<script>` tag as such:
246
247```html
248<script src="lib/random-js.min.js"></script>
249<script>
250 // Random is now available as a global (on the window object)
251 var random = new Random.Random();
252 alert("Random value from 1 to 100: " + random.integer(1, 100));
253</script>
254```
255
256## Extending
257
258You can add your own methods to `Random` instances, as such:
259
260```js
261var random = new Random();
262random.bark = function() {
263 if (this.bool()) {
264 return "arf!";
265 } else {
266 return "woof!";
267 }
268};
269random.bark(); //=> "arf!" or "woof!"
270```
271
272This is the recommended approach, especially if you only use one instance of `Random`.
273
274Or you could even make your own subclass of Random:
275
276```js
277function MyRandom(engine) {
278 return Random.call(this, engine);
279}
280MyRandom.prototype = Object.create(Random.prototype);
281MyRandom.prototype.constructor = MyRandom;
282MyRandom.prototype.mood = function() {
283 switch (this.integer(0, 2)) {
284 case 0:
285 return "Happy";
286 case 1:
287 return "Content";
288 case 2:
289 return "Sad";
290 }
291};
292var random = new MyRandom();
293random.mood(); //=> "Happy", "Content", or "Sad"
294```
295
296Or, if you have a build tool are are in an ES6+ environment:
297
298```js
299class MyRandom extends Random {
300 mood() {
301 switch (this.integer(0, 2)) {
302 case 0:
303 return "Happy";
304 case 1:
305 return "Content";
306 case 2:
307 return "Sad";
308 }
309 }
310}
311const random = new MyRandom();
312random.mood(); //=> "Happy", "Content", or "Sad"
313```
314
315## Testing
316
317All the code in Random.js is fully tested and covered using `jest`.
318
319To run tests in node.js:
320
321```sh
322npm install
323npm test
324```
325
326or
327
328```sh
329yarn install
330yarn test
331```
332
333## License
334
335The MIT License (MIT).
336
337See the LICENSE file in this project for more details.