Skip to content

Commit

Permalink
Dev new prng distribution (#9)
Browse files Browse the repository at this point in the history
* 2.7.1 - Xorshift PRNG added, PRNG proxy refactored, tests added

* 2.7.2 - Kiss PRNG added

* 2.7.3 - ParkMiller PRNG added, tests added, minor fixes

* 2.7.4 - Coveyou PRNG added, tests added, minor fixes

* 2.7.5 - Knuthran2 RPNG added, tests added, minor fixes

* 2.7.6 - r250 PRNG added, tests added

* 2.7.7 - mrg5 PRNG added, tests added, minor fixes

* 2.7.8 - gfsr4 PRNG added, tests added

* 2.7.9 - dx1597 PRNG added, tests added

* 2.7.10 - tt800 PRNG added, tests added

* 2.7.11 - Xorwow PRNG added

* Readme extended

Authored-by: Alexey Kiselev <alexeys.kiselev@yahoo.com>
  • Loading branch information
AlexeySKiselev authored May 18, 2020
1 parent 7f739e9 commit 13f24ce
Show file tree
Hide file tree
Showing 20 changed files with 1,858 additions and 86 deletions.
22 changes: 21 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Unirand
A JavaScript module for generating seeded random distributions and its statistical analysis.

Implemented in pure JavaScript with no dependencies, designed to work in Node.js and fully asynchronous, tested *with 600+ tests*.
Implemented in pure JavaScript with no dependencies, designed to work in Node.js and fully asynchronous, tested *with 740+ tests*.

#### [Supported distributions](./core/methods/)

Expand Down Expand Up @@ -58,6 +58,24 @@ Also you can set another PRNG by calling:
unirand.prng.set_prng('default'); // now PRNG is default JS generator equals to Math.random()
```

Unirand supports different PRNGs:

| Name | Description | Performance | Supports seed |
|---|---|---|---|
| `default` | Default JS PRNG | fast | No |
| `tuchei` | Tuchei PRNG, period ~2<sup>32</sup> | very fast | Yes |
| `xorshift` | Xorshift PRNG, period ~2<sup>32</sup> | very fast | Yes |
| `kiss` | Kiss PRNG, period ~2<sup>30</sup> | fast | Yes |
| `parkmiller` | Park-Miller PRNG, period ~2<sup>31</sup> | medium | Yes |
| `coveyou` | Coveyou PRNG, period ~2<sup>31</sup> | slow | Yes |
| `knuthran2` | knuthran2 PRNG, period ~10<sup>18</sup> | slow | Yes |
| `r250` | r250 PRNG, period ~2<sup>250</sup> | very fast | Yes |
| `mrg5` | Fifth-order multiple recursive PRNG, period ~10<sup>46</sup> | slow | Yes |
| `gfsr4` | gfsr4 PRNG, period ~2<sup>9689</sup> | fast | Yes |
| `dx1597` | Dx-1957-f PRNG, period ~10<sup>14903</sup> | slow | Yes |
| `tt800` | TT800 PRNG, period ~10<sup>240</sup> | medium | Yes |
| `xorwow` | Xorwow PRNG, period ~10<sup>38</sup> | medium | Yes |

#### .random() and .randomInt()
Returns random uniformly distributed value or array of length *n*. Returns different value each time without seed and same value with seed value.
```javascript
Expand All @@ -81,6 +99,8 @@ unirand.next(); // returns 0.045074593275785446
```
Same results for `.nextInt()`. These methods always return single value.

\**Note*: for seeded prng we don't recommend use `.random()` method for generating all random values. Use `.random()` first time flushing generator, then `.next()` for all other random values.

#### .seed()
```javascript
unirand.seed('unirand'); // sets seed value for PRNG
Expand Down
5 changes: 3 additions & 2 deletions core/analyzer/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,9 @@ class Common implements IAnalyzerSingleton {
*/
let cumulative_step: number = values_step;
for(let i = 1; i < pdf.length; i += 1) {
if(pdf[i] !== 0)
if(pdf[i] > 0.000001) {
break;
}
cumulative_step += values_step;
}

Expand All @@ -251,7 +252,7 @@ class Common implements IAnalyzerSingleton {

// Calculate entropy
cumulative_step += values_step;
if(pdf[i] !== 0 && pdf[i] !== 1) {
if(pdf[i] > 0.000001 && pdf[i] < 0.999999) {
this._entropy -= (cumulative_step === 0)?0:(pdf[i] * Math.log(pdf[i] / cumulative_step));
cumulative_step = 0;
}
Expand Down
57 changes: 48 additions & 9 deletions core/prng/BasicPRNG.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Generates random numbers with seed
*/
import type { IPRNG } from '../interfaces';
import type { NumberString, RandomArrayNumber } from '../types';
import type {NumberString, RandomArray, RandomArrayNumber} from '../types';
import hashProxy from '../utils/hash';

class BasicPRNG implements IPRNG {
Expand All @@ -16,13 +16,26 @@ class BasicPRNG implements IPRNG {
}

/**
* Random number generator with seed\
* @abstract
* Random number generator with seed
* @returns {number} random number
*/
// eslint-disable-next-line
random(n: ?number = 1): RandomArrayNumber {
throw new Error('Unassigned method');
this._prepare_initial();

if (typeof n !== 'number') {
return this.next();
}

if (n <= 1) {
return this.next();
}

const random_array: RandomArray = [];
for (let i = 0; i < n; i += 1) {
random_array[i] = this.next();
}

return random_array;
}

/**
Expand All @@ -40,20 +53,33 @@ class BasicPRNG implements IPRNG {
* Next integer random value
* Returns only single random value
* Does not support seed
* @abstract
* @returns {number}
*/
nextInt(): number {
throw new Error('Unassigned method');
return this._nextInt();
}

/**
* Generates random integer [0, 2^32)
* @returns {number}
*/
// eslint-disable-next-line
randomInt(n: ?number = 1): RandomArrayNumber {
throw new Error('Unassigned method');
this._prepare_initial();

if (typeof n !== 'number') {
return this.nextInt();
}

if (n <= 1) {
return this.nextInt();
}

const random_array: RandomArray = [];
for (let i = 0; i < n; i += 1) {
random_array[i] = this.nextInt();
}

return random_array;
}

/**
Expand Down Expand Up @@ -82,6 +108,19 @@ class BasicPRNG implements IPRNG {
}
return _seed;
}

/**
* Prepare initial values for calculating random value
* @private
*/
_prepare_initial(): void {
if (this._no_seed === true) {
this._initialize();
this._set_random_seed();
} else {
this._get_from_state();
}
}
}

export default BasicPRNG;
156 changes: 156 additions & 0 deletions core/prng/CoveyouPRNG.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// @flow
/**
* Coveyou PRNG
* It is taken from Knuth’s Seminumerical Algorithms, 3rd Ed., Section 3.2.2.
* Period: 2^31 - 1
*/

import BasicPRNG from './BasicPRNG';
import type { IPRNG } from '../interfaces';
import type {NumberString} from '../types';

class CoveyouPRNG extends BasicPRNG implements IPRNG {

_M: number;
_x: number;
_modulos: Array<number>; // pre calculated modulo of 10^x mod _M
_no_seed: boolean;
_state: {[prop: string]: number}; // state after setting seed

constructor() {
super();
this._no_seed = true;
this._state = {};
this._M = 0x100000000;
// to be recalculated since _M changed
this._modulos = [
1, // 10^0 mod _M
10, // 10^1 mod _M
100, // 10^2 mod _M
1000, // 10^3 mod _M
10000, // 10^4 mod _M
100000, // 10^5 mod _M
1000000, // 10^6 mod _M
10000000, // 10^7 mod _M
100000000, // 10^8 mod _M
1000000000, // 10^9 mod _M
1410065408, // 10^10 mod _M
1215752192, // 10^11 mod _M
3567587328, // 10^12 mod _M
1316134912, // 10^13 mod _M
276447232, // 10^14 mod _M
2764472320, // 10^15 mod _M
1874919424, // 10^16 mod _M
1569325056, // 10^17 mod _M
2808348672 // 10^18 mod _M
];
this._initialize();
}

/**
* Initializes initial values and sets state for calculating random number
* @private
*/
_initialize(): void {
this._x = 0;
}

/**
* Sets state for random number generating
* @param {number} x
* @private
*/
_setState(x: number): void {
this._state._x = x;
}

/**
* Gets values from state
* @private
*/
_get_from_state(): void {
this._x = this._state._x;
}

/**
* Creates random seed
* @private
*/
_set_random_seed(): void {
this._seed = BasicPRNG.random_seed();
this._x = this._seed % this._M;
}

/**
* @override
* @param {?NumberString} seed_value
*/
seed(seed_value: ?NumberString): void {
this._initialize();
if (seed_value === undefined || seed_value === null) {
this._no_seed = true;
} else if (typeof seed_value === 'number') {
this._seed = Math.floor(seed_value);
this._x = this._seed % this._M;
this._setState(this._x);
this._no_seed = false;
} else if (typeof seed_value === 'string') {
this._seed = seed_value;
for (let i = 0; i < this._seed.length; i += 1) {
this._x = (this._x + this._seed.charCodeAt(i)) % this._M;
this._nextInt();
}
this._setState(this._x);
this._no_seed = false;
} else {
this._no_seed = true;
throw new Error('You should point seed with types: "undefined", "number" or "string"');
}
}

/**
* Squares value with modulo
* Need it for more precise calculation
* @private
*/
_squareWithModulo(x: number): number {
// extract data from x
const xData: Array<number> = [0];
let _x: number = x;
let i: number = 0;
while (_x > 0) {
xData[i] = _x % 10;
_x = Math.floor(_x / 10);
i += 1;
}

let res: number = 0;
for (let i = 0; i < xData.length; i += 1) {
// add squares
res = (res + xData[i] * ((xData[i] * this._modulos[2 * i]) % this._M)) % this._M;

// add other parts
for (let j = i + 1; j < xData.length; j += 1) {
res = (res + 2 * xData[i] * ((xData[j] * this._modulos[i + j]) % this._M)) % this._M;
}
}

return res;
}

_nextInt(): number {
let x: number = this._x;
x = (this._squareWithModulo(x) + x) % this._M;
return this._x = x;
}

/**
* @override
* @returns {number}
*/
next(): number {
return (this._nextInt() >>> 0) / this._M;
}
}

export default CoveyouPRNG;
2 changes: 1 addition & 1 deletion core/prng/DefaultPRNG.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class DefaultPRNG extends BasicPRNG implements IPRNG {
}

_randomInt(): number {
return this._random() * 0x100000000;
return Math.floor(this._random() * 0x100000000);
}

random(n: ?number = 1): RandomArrayNumber {
Expand Down
Loading

0 comments on commit 13f24ce

Please sign in to comment.