Skip to content

Commit 0f29c23

Browse files
feat: implement an initial version of pcg32 prng
--- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: na - task: lint_package_json status: na - task: lint_repl_help status: na - task: lint_javascript_src status: passed - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: passed - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: na - task: lint_typescript_tests status: na - task: lint_license_headers status: passed ---
1 parent f38e07c commit 0f29c23

File tree

5 files changed

+274
-268
lines changed

5 files changed

+274
-268
lines changed

lib/node_modules/@stdlib/random/base/pcg32/lib/factory.js

Lines changed: 128 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,30 @@ var isObject = require( '@stdlib/assert/is-plain-object' );
3030
var isBoolean = require( '@stdlib/assert/is-boolean' ).isPrimitive;
3131
var isCollection = require( '@stdlib/assert/is-collection' );
3232
var isPositiveInteger = require( '@stdlib/assert/is-positive-integer' ).isPrimitive;
33-
var isInt32Array = require( '@stdlib/assert/is-int32array' );
33+
var isUint32Array = require( '@stdlib/assert/is-uint32array' );
3434
var format = require( '@stdlib/string/format' );
35-
var INT32_MAX = require( '@stdlib/constants/int32/max' );
36-
var Int32Array = require( '@stdlib/array/int32' );
35+
var UINT32_MAX = require( '@stdlib/constants/uint32/max' );
36+
var Uint32Array = require( '@stdlib/array/uint32' );
3737
var gcopy = require( '@stdlib/blas/base/gcopy' );
3838
var typedarray2json = require( '@stdlib/array/to-json' );
39-
var randint32 = require( './rand_int32.js' );
39+
var umul = require( '@stdlib/number/uint32/base/mul' );
40+
var umuldw = require( '@stdlib/number/uint32/base/muldw' );
41+
var randuint32 = require( './rand_uint32.js' );
4042

4143

4244
// VARIABLES //
4345

44-
var NORMALIZATION_CONSTANT = (INT32_MAX - 1)|0; // asm type annotation
45-
var MAX_SEED = (INT32_MAX - 1)|0; // asm type annotation
46-
var A = 16807|0; // asm type annotation
46+
var NORMALIZATION_CONSTANT = UINT32_MAX + 1;
47+
var MAX_SEED = UINT32_MAX >>> 0; // asm type annotation
48+
49+
// LCG multiplier
50+
var MULTIPLIER = [ 0x4C957F2D >>> 0, 0x5851F42D >>> 0 ]; // asm type annotation
51+
52+
// LCG increment, controls the stream. #TODO: Make user definable like seed
53+
var INCREMENT = [ 0x0000DEAD >>> 0, 0xBEEF0000 >>> 0 ]; // asm type annotation
54+
55+
// Define the size of state as a multiple of 32-bits
56+
var N = 2;
4757

4858
// Define the state array schema version:
4959
var STATE_ARRAY_VERSION = 1; // NOTE: anytime the state array schema changes, this value should be incremented!!!
@@ -55,10 +65,10 @@ var NUM_STATE_SECTIONS = 2; // state, seed
5565
var STATE_SECTION_OFFSET = 2; // | version | num_sections | state_length | ...state | seed_length | ...seed |
5666

5767
// Define the index offset of the seed section in the state array:
58-
var SEED_SECTION_OFFSET = 4; // | version | num_sections | state_length | ...state | seed_length | ...seed |
68+
var SEED_SECTION_OFFSET = 3 + N; // | version | num_sections | state_length | ...state | seed_length | ...seed |
5969

6070
// Define the length of the "fixed" length portion of the state array:
61-
var STATE_FIXED_LENGTH = 5; // 1 (version) + 1 (num_sections) + 1 (state_length) + 1 (state) + 1 (seed_length)
71+
var STATE_FIXED_LENGTH = 4 + N; // 1 (version) + 1 (num_sections) + 1 (state_length) + 2 (state) + 1 (seed_length)
6272

6373

6474
// FUNCTIONS //
@@ -67,7 +77,7 @@ var STATE_FIXED_LENGTH = 5; // 1 (version) + 1 (num_sections) + 1 (state_length)
6777
* Verifies state array integrity.
6878
*
6979
* @private
70-
* @param {Int32Array} state - state array
80+
* @param {Uint32Array} state - state array
7181
* @param {boolean} FLG - flag indicating whether the state array was provided as an option (true) or an argument (false)
7282
* @returns {(Error|null)} an error or `null`
7383
*/
@@ -90,11 +100,11 @@ function verifyState( state, FLG ) {
90100
if ( state[ 1 ] !== NUM_STATE_SECTIONS ) {
91101
return new RangeError( format( 'invalid %s. State array has an incompatible number of sections. Expected: `%s`. Actual: `%s`.', s1, NUM_STATE_SECTIONS, state[ 1 ] ) );
92102
}
93-
// The length of the "state" section must equal `1`...
94-
if ( state[ STATE_SECTION_OFFSET ] !== 1 ) {
95-
return new RangeError( format( 'invalid %s. State array has an incompatible state length. Expected: `%u`. Actual: `%u`.', s1, 1, state[ STATE_SECTION_OFFSET ] ) );
103+
// The length of the "state" section must equal `N`...
104+
if ( state[ STATE_SECTION_OFFSET ] !== N ) {
105+
return new RangeError( format( 'invalid %s. State array has an incompatible state length. Expected: `%u`. Actual: `%u`.', s1, 2, state[ STATE_SECTION_OFFSET ] ) );
96106
}
97-
// The length of the "seed" section much match the empirical length...
107+
// The length of the "seed" section must match the empirical length...
98108
if ( state[ SEED_SECTION_OFFSET ] !== state.length-STATE_FIXED_LENGTH ) {
99109
return new RangeError( format( 'invalid %s. State array length is incompatible with seed section length. Expected: `%u`. Actual: `%u`.', s1, state.length-STATE_FIXED_LENGTH, state[ SEED_SECTION_OFFSET ] ) );
100110
}
@@ -112,9 +122,9 @@ function verifyState( state, FLG ) {
112122
* @param {PRNGStateMINSTD} [options.state] - pseudorandom number generator state
113123
* @param {boolean} [options.copy=true] - boolean indicating whether to copy a provided pseudorandom number generator state
114124
* @throws {TypeError} options argument must be an object
115-
* @throws {TypeError} a seed must be either a positive integer less than the maximum signed 32-bit integer or an array-like object containing integers less than the maximum signed 32-bit integer
116-
* @throws {RangeError} a numeric seed must be a positive integer less than the maximum signed 32-bit integer
117-
* @throws {TypeError} state must be an `Int32Array`
125+
* @throws {TypeError} a seed must be either a positive integer less than the maximum unsigned 32-bit integer or an array-like object containing integers less than the maximum unsigned 32-bit integer
126+
* @throws {RangeError} a numeric seed must be a positive integer less than the maximum unsigned 32-bit integer
127+
* @throws {TypeError} state must be an `Uint32Array`
118128
* @throws {Error} must provide a valid state
119129
* @throws {TypeError} `copy` option must be a boolean
120130
* @returns {PRNG} LCG PRNG
@@ -132,7 +142,7 @@ function verifyState( state, FLG ) {
132142
* });
133143
*
134144
* var v = minstd();
135-
* // returns 20739838
145+
* // returns 2557507945
136146
*/
137147
function factory( options ) {
138148
var STATE;
@@ -156,8 +166,8 @@ function factory( options ) {
156166
if ( hasOwnProp( options, 'state' ) ) {
157167
state = options.state;
158168
opts.state = true;
159-
if ( !isInt32Array( state ) ) {
160-
throw new TypeError( format( 'invalid option. `%s` option must be an Int32Array. Option: `%s`.', 'state', state ) );
169+
if ( !isUint32Array( state ) ) {
170+
throw new TypeError( format( 'invalid option. `%s` option must be an Uint32Array. Option: `%s`.', 'state', state ) );
161171
}
162172
err = verifyState( state, true );
163173
if ( err ) {
@@ -166,14 +176,14 @@ function factory( options ) {
166176
if ( opts.copy === false ) {
167177
STATE = state;
168178
} else {
169-
STATE = new Int32Array( state.length );
179+
STATE = new Uint32Array( state.length );
170180
gcopy( state.length, state, 1, STATE, 1 );
171181
}
172182
// Create a state "view":
173-
state = new Int32Array( STATE.buffer, STATE.byteOffset+((STATE_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), 1 );
183+
state = new Uint32Array( STATE.buffer, STATE.byteOffset+((STATE_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), N );
174184

175185
// Create a seed "view":
176-
seed = new Int32Array( STATE.buffer, STATE.byteOffset+((SEED_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), state[ SEED_SECTION_OFFSET ] );
186+
seed = new Uint32Array( STATE.buffer, STATE.byteOffset+((SEED_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), state[ SEED_SECTION_OFFSET ] );
177187
}
178188
// If provided a PRNG state, we ignore the `seed` option...
179189
if ( seed === void 0 ) {
@@ -182,68 +192,76 @@ function factory( options ) {
182192
opts.seed = true;
183193
if ( isPositiveInteger( seed ) ) {
184194
if ( seed > MAX_SEED ) {
185-
throw new RangeError( format( 'invalid option. `%s` option must be a positive integer less than the maximum signed 32-bit integer. Option: `%u`.', 'seed', seed ) );
195+
throw new RangeError( format( 'invalid option. `%s` option must be a positive integer less than the maximum unsigned 32-bit integer. Option: `%u`.', 'seed', seed ) );
186196
}
187-
seed |= 0; // asm type annotation
197+
seed >>>= 0; // asm type annotation
188198
} else if ( isCollection( seed ) && seed.length > 0 ) {
189199
slen = seed.length;
190-
STATE = new Int32Array( STATE_FIXED_LENGTH+slen );
200+
STATE = new Uint32Array( STATE_FIXED_LENGTH+slen );
191201

192202
// Initialize sections:
193203
STATE[ 0 ] = STATE_ARRAY_VERSION;
194204
STATE[ 1 ] = NUM_STATE_SECTIONS;
195-
STATE[ STATE_SECTION_OFFSET ] = 1;
205+
STATE[ STATE_SECTION_OFFSET ] = N;
196206
STATE[ SEED_SECTION_OFFSET ] = slen;
197207

198208
// Copy the provided seed array to prevent external mutation, as mutation would lead to an inability to reproduce PRNG values according to the PRNG's stated seed:
199209
gcopy.ndarray( slen, seed, 1, 0, STATE, 1, SEED_SECTION_OFFSET+1 );
200210

201211
// Create a state "view":
202-
state = new Int32Array( STATE.buffer, STATE.byteOffset+((STATE_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), 1 );
212+
state = new Uint32Array( STATE.buffer, STATE.byteOffset+((STATE_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), N );
203213

204214
// Create a seed "view":
205-
seed = new Int32Array( STATE.buffer, STATE.byteOffset+((SEED_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), slen );
215+
seed = new Uint32Array( STATE.buffer, STATE.byteOffset+((SEED_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), slen );
206216

207217
// Initialize the internal PRNG state:
208-
state[ 0 ] = seed[ 0 ];
218+
minstd();
219+
state.set( addUint64( state, [
220+
seed[ 0 ],
221+
( slen > 1 ) ? seed[ 1 ] : 0
222+
]) );
223+
minstd();
209224
} else {
210-
throw new TypeError( format( 'invalid option. `%s` option must be either a positive integer less than the maximum signed 32-bit integer or an array-like object containing integer values less than the maximum signed 32-bit integer. Option: `%s`.', 'seed', seed ) );
225+
throw new TypeError( format( 'invalid option. `%s` option must be either a positive integer less than the maximum unsigned 32-bit integer or an array-like object containing integer values less than the maximum unsigned 32-bit integer. Option: `%s`.', 'seed', seed ) );
211226
}
212227
} else {
213-
seed = randint32()|0; // asm type annotation
228+
seed = randuint32() >>> 0; // asm type annotation
214229
}
215230
}
216231
} else {
217-
seed = randint32()|0; // asm type annotation
232+
seed = randuint32() >>> 0; // asm type annotation
218233
}
219234
if ( state === void 0 ) {
220-
STATE = new Int32Array( STATE_FIXED_LENGTH+1 );
235+
STATE = new Uint32Array( STATE_FIXED_LENGTH+1 );
221236

222237
// Initialize sections:
223238
STATE[ 0 ] = STATE_ARRAY_VERSION;
224239
STATE[ 1 ] = NUM_STATE_SECTIONS;
225-
STATE[ STATE_SECTION_OFFSET ] = 1;
240+
STATE[ STATE_SECTION_OFFSET ] = N;
226241
STATE[ SEED_SECTION_OFFSET ] = 1;
227242
STATE[ SEED_SECTION_OFFSET+1 ] = seed;
228243

229244
// Create a state "view":
230-
state = new Int32Array( STATE.buffer, STATE.byteOffset+((STATE_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), 1 );
245+
state = new Uint32Array( STATE.buffer, STATE.byteOffset+((STATE_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), N );
231246

232247
// Create a seed "view":
233-
seed = new Int32Array( STATE.buffer, STATE.byteOffset+((SEED_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), 1 );
248+
seed = new Uint32Array( STATE.buffer, STATE.byteOffset+((SEED_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), 1 );
234249

235250
// Initialize the internal PRNG state:
236-
state[ 0 ] = seed[ 0 ];
251+
minstd();
252+
state.set( addUint64( state, [ seed[ 0 ], 0 ] ) );
253+
minstd();
237254
}
255+
238256
setReadOnly( minstd, 'NAME', 'minstd' );
239257
setReadOnlyAccessor( minstd, 'seed', getSeed );
240258
setReadOnlyAccessor( minstd, 'seedLength', getSeedLength );
241259
setReadWriteAccessor( minstd, 'state', getState, setState );
242260
setReadOnlyAccessor( minstd, 'stateLength', getStateLength );
243261
setReadOnlyAccessor( minstd, 'byteLength', getStateSize );
244262
setReadOnly( minstd, 'toJSON', toJSON );
245-
setReadOnly( minstd, 'MIN', 1 );
246-
setReadOnly( minstd, 'MAX', INT32_MAX-1 );
263+
setReadOnly( minstd, 'MIN', 0 );
264+
setReadOnly( minstd, 'MAX', UINT32_MAX );
247265
setReadOnly( minstd, 'normalized', normalized );
248266

249267
setReadOnly( normalized, 'NAME', minstd.NAME );
@@ -253,8 +271,8 @@ function factory( options ) {
253271
setReadOnlyAccessor( normalized, 'stateLength', getStateLength );
254272
setReadOnlyAccessor( normalized, 'byteLength', getStateSize );
255273
setReadOnly( normalized, 'toJSON', toJSON );
256-
setReadOnly( normalized, 'MIN', (minstd.MIN-1.0) / NORMALIZATION_CONSTANT );
257-
setReadOnly( normalized, 'MAX', (minstd.MAX-1.0) / NORMALIZATION_CONSTANT );
274+
setReadOnly( normalized, 'MIN', minstd.MIN / NORMALIZATION_CONSTANT );
275+
setReadOnly( normalized, 'MAX', minstd.MAX / NORMALIZATION_CONSTANT );
258276

259277
return minstd;
260278

@@ -266,7 +284,7 @@ function factory( options ) {
266284
*/
267285
function getSeed() {
268286
var len = STATE[ SEED_SECTION_OFFSET ];
269-
return gcopy( len, seed, 1, new Int32Array( len ), 1 );
287+
return gcopy( len, seed, 1, new Uint32Array( len ), 1 );
270288
}
271289

272290
/**
@@ -321,7 +339,7 @@ function factory( options ) {
321339
*/
322340
function getState() {
323341
var len = STATE.length;
324-
return gcopy( len, STATE, 1, new Int32Array( len ), 1 );
342+
return gcopy( len, STATE, 1, new Uint32Array( len ), 1 );
325343
}
326344

327345
/**
@@ -334,13 +352,13 @@ function factory( options ) {
334352
*
335353
* @private
336354
* @param {PRNGStateMINSTD} s - generator state
337-
* @throws {TypeError} must provide an `Int32Array`
355+
* @throws {TypeError} must provide an `Uint32Array`
338356
* @throws {Error} must provide a valid state
339357
*/
340358
function setState( s ) {
341359
var err;
342-
if ( !isInt32Array( s ) ) {
343-
throw new TypeError( format( 'invalid argument. Must provide an Int32Array. Value: `%s`.', s ) );
360+
if ( !isUint32Array( s ) ) {
361+
throw new TypeError( format( 'invalid argument. Must provide an Uint32Array. Value: `%s`.', s ) );
344362
}
345363
err = verifyState( s, false );
346364
if ( err ) {
@@ -356,15 +374,15 @@ function factory( options ) {
356374
} else {
357375
// Check if we can reuse allocated memory...
358376
if ( s.length !== STATE.length ) {
359-
STATE = new Int32Array( s.length ); // reallocate
377+
STATE = new Uint32Array( s.length ); // reallocate
360378
}
361379
gcopy( s.length, s, 1, STATE, 1 );
362380
}
363381
// Create a new state "view":
364-
state = new Int32Array( STATE.buffer, STATE.byteOffset+((STATE_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), 1 );
382+
state = new Uint32Array( STATE.buffer, STATE.byteOffset+((STATE_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), N );
365383

366384
// Create a new seed "view":
367-
seed = new Int32Array( STATE.buffer, STATE.byteOffset+((SEED_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), STATE[ SEED_SECTION_OFFSET ] );
385+
seed = new Uint32Array( STATE.buffer, STATE.byteOffset+((SEED_SECTION_OFFSET+1)*STATE.BYTES_PER_ELEMENT), STATE[ SEED_SECTION_OFFSET ] );
368386
}
369387

370388
/**
@@ -387,16 +405,69 @@ function factory( options ) {
387405
}
388406

389407
/**
390-
* Generates a pseudorandom integer on the interval \\( [1,2^{31}-1) \\).
408+
* Generates a pseudorandom integer on the interval \\( [1,2^{32}) \\).
391409
*
392410
* @private
393-
* @returns {integer32} pseudorandom integer
411+
* @returns {uinteger32} pseudorandom integer
394412
*/
395413
function minstd() {
396-
var s = state[ 0 ]|0; // asm type annotation
397-
s = ( (A*s)%INT32_MAX )|0; // asm type annotation
398-
state[ 0 ] = s;
399-
return s|0; // asm type annotation
414+
var xorshifted = [ 0, 0 ];
415+
var oldstate = [ state[ 0 ], state[ 1 ] ];
416+
var rot;
417+
418+
state.set( addUint64( multUint64( oldstate, MULTIPLIER ), INCREMENT ) );
419+
420+
xorshifted[0] = ( ( oldstate[0] >>> 18 ) | ( oldstate[1] << 14 ) ) >>> 0;
421+
xorshifted[1] = ( oldstate[1] >>> 18 ) >>> 0;
422+
423+
xorshifted[0] ^= oldstate[0];
424+
xorshifted[1] ^= oldstate[1];
425+
426+
xorshifted[0] = ( ( xorshifted[0] >>> 27 ) | ( xorshifted[1] << 5 ) ) >>> 0;
427+
xorshifted[1] = ( xorshifted[1] >>> 27 ) >>> 0;
428+
429+
rot = oldstate[1] >>> 27;
430+
return ( (xorshifted[0] >>> rot) | ( xorshifted[0] << ( -rot & 31 ) ) ) >>> 0;
431+
}
432+
433+
/**
434+
* Adds two 64-bit unsigned integers.
435+
*
436+
* @private
437+
* @param {ArrayLike} a - 64-bit unsigned integer as [ low, high ]
438+
* @param {ArrayLike} b - 64-bit unsigned integer as [ low, high ]
439+
* @returns {ArrayLike} 64-bit unsigned integer sum as [ low, high ]
440+
*/
441+
function addUint64( a, b ) {
442+
var la = a[0] >>> 0;
443+
var ha = a[1] >>> 0;
444+
var lb = b[0] >>> 0;
445+
var hb = b[1] >>> 0;
446+
447+
var ls = ( la + lb ) >>> 0;
448+
var hs = ( ha + hb + ( ls < la ) ) >>> 0;
449+
return [ ls >>> 0, hs >>> 0 ];
450+
}
451+
452+
/**
453+
* Multiplies two 64-bit unsigned integers.
454+
*
455+
* @private
456+
* @param {ArrayLike} a - 64-bit unsigned integer as [ low, high ]
457+
* @param {ArrayLike} b - 64-bit unsigned integer as [ low, high ]
458+
* @returns {ArrayLike} 64-bit unsigned integer product as [ low, high ]
459+
*/
460+
function multUint64( a, b ) {
461+
var la = a[0] >>> 0;
462+
var ha = a[1] >>> 0;
463+
var lb = b[0] >>> 0;
464+
var hb = b[1] >>> 0;
465+
var m1 = umul( la, hb ) >>> 0;
466+
var m2 = umul( ha, lb ) >>> 0;
467+
var p = umuldw( la, lb );
468+
p = [ p[ 1 ], p[ 0 ] ]; // swap to make little-endian
469+
p[ 1 ] = ( p[ 1 ] + m1 + m2 ) >>> 0;
470+
return p;
400471
}
401472

402473
/**
@@ -406,7 +477,7 @@ function factory( options ) {
406477
* @returns {number} pseudorandom number
407478
*/
408479
function normalized() {
409-
return (minstd()-1) / NORMALIZATION_CONSTANT;
480+
return minstd() / NORMALIZATION_CONSTANT;
410481
}
411482
}
412483

lib/node_modules/@stdlib/random/base/pcg32/lib/main.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
// MODULES //
2222

2323
var factory = require( './factory.js' );
24-
var randint32 = require( './rand_int32.js' );
24+
var randuint32 = require( './rand_uint32.js' );
2525

2626

2727
// MAIN //
@@ -93,7 +93,7 @@ var randint32 = require( './rand_int32.js' );
9393
* // returns <number>
9494
*/
9595
var minstd = factory({
96-
'seed': randint32()
96+
'seed': randuint32()
9797
});
9898

9999

0 commit comments

Comments
 (0)