Skip to content

Commit

Permalink
💥 Require generators uniform in int32 (#513)
Browse files Browse the repository at this point in the history
In the past, allowed them to customize the range of values they can produce. We now enforce a specific range for an higher throughput of the lib.
  • Loading branch information
dubzzz authored Jan 9, 2023
1 parent b4852a8 commit c45912f
Show file tree
Hide file tree
Showing 12 changed files with 26 additions and 93 deletions.
4 changes: 2 additions & 2 deletions src/distribution/UnsafeUniformBigIntDistribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ const SBigInt: typeof BigInt = typeof BigInt !== 'undefined' ? BigInt : undefine
*/
export function unsafeUniformBigIntDistribution(from: bigint, to: bigint, rng: RandomGenerator): bigint {
const diff = to - from + SBigInt(1);
const MinRng = SBigInt(rng.min());
const NumValues = SBigInt(rng.max() - rng.min() + 1);
const MinRng = SBigInt(-0x80000000);
const NumValues = SBigInt(0x100000000);

// Number of iterations required to have enough random
// to build uniform entries in the asked range
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { RandomGenerator } from '../../generator/RandomGenerator';
* @internal
*/
export function unsafeUniformIntDistributionInternal(rangeSize: number, rng: RandomGenerator): number {
const MinRng = rng.min();
const NumValues = rng.max() - rng.min() + 1;
const MinRng = -0x80000000;
const NumValues = 0x100000000;

// Range provided by the RandomGenerator is large enough
if (rangeSize <= NumValues) {
Expand Down
11 changes: 0 additions & 11 deletions src/generator/LinearCongruential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,8 @@ const computeValueFromNextSeed = function (nextseed: number) {
};

class LinearCongruential32 implements RandomGenerator {
static readonly min: number = -0x80000000;
static readonly max: number = 0x7fffffff;

constructor(private seed: number) {}

min(): number {
return LinearCongruential32.min;
}

max(): number {
return LinearCongruential32.max;
}

clone(): LinearCongruential32 {
return new LinearCongruential32(this.seed);
}
Expand Down
11 changes: 0 additions & 11 deletions src/generator/MersenneTwister.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { RandomGenerator } from './RandomGenerator';

class MersenneTwister implements RandomGenerator {
static readonly min: number = -0x80000000;
static readonly max: number = 0x7fffffff;

static readonly N = 624;
static readonly M = 397;
static readonly R = 31;
Expand Down Expand Up @@ -51,14 +48,6 @@ class MersenneTwister implements RandomGenerator {
return new MersenneTwister(MersenneTwister.twist(MersenneTwister.seeded(seed)), 0);
}

min(): number {
return MersenneTwister.min;
}

max(): number {
return MersenneTwister.max;
}

clone(): MersenneTwister {
return new MersenneTwister(this.states, this.index);
}
Expand Down
14 changes: 8 additions & 6 deletions src/generator/RandomGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
export interface RandomGenerator {
/** Minimal value (included) that could be generated by this generator */
min(): number;
/** Maximal value (included) that could be generated by this generator */
max(): number;
/** Produce a fully independent clone of the current instance */
clone(): RandomGenerator;
/** Generate next random value along with the next generator (does not impact current instance) */
/**
* Generate next random value along with the next generator (does not impact current instance).
* Values uniform in range -0x8000_0000 (included) to 0x7fff_ffff (included)
*/
next(): [number, RandomGenerator];
/** Compute the jumped generator (does not impact current instance) */
jump?(): RandomGenerator;
/** Generate next value BUT alters current generator */
/**
* Generate next value BUT alters current generator.
* Values uniform in range -0x8000_0000 (included) to 0x7fff_ffff (included)
*/
unsafeNext(): number;
/** Jump current generator */
unsafeJump?(): void;
Expand Down
6 changes: 0 additions & 6 deletions src/generator/XorShift.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@ import { RandomGenerator } from './RandomGenerator';
// See https://github.com/v8/v8/blob/4b9b23521e6fd42373ebbcb20ebe03bf445494f9/src/base/utils/random-number-generator.h#L119-L128
class XorShift128Plus implements RandomGenerator {
constructor(private s01: number, private s00: number, private s11: number, private s10: number) {}
min(): number {
return -0x80000000;
}
max(): number {
return 0x7fffffff;
}
clone(): XorShift128Plus {
return new XorShift128Plus(this.s01, this.s00, this.s11, this.s10);
}
Expand Down
6 changes: 0 additions & 6 deletions src/generator/XoroShiro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@ import { RandomGenerator } from './RandomGenerator';
// - http://prng.di.unimi.it/xoroshiro128plus.c
class XoroShiro128Plus implements RandomGenerator {
constructor(private s01: number, private s00: number, private s11: number, private s10: number) {}
min(): number {
return -0x80000000;
}
max(): number {
return 0x7fffffff;
}
clone(): XoroShiro128Plus {
return new XoroShiro128Plus(this.s01, this.s00, this.s11, this.s10);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ import { uniformBigIntDistribution } from '../../../src/distribution/UniformBigI
import mersenne from '../../../src/generator/MersenneTwister';
import { RandomGenerator } from '../../../src/generator/RandomGenerator';

const MERSENNE_MIN = mersenne(0).min();
const MERSENNE_MAX = mersenne(0).max();

describe('uniformBigIntDistribution [non regression]', () => {
if (typeof BigInt === 'undefined') {
it('no test', () => {
Expand All @@ -19,9 +16,9 @@ describe('uniformBigIntDistribution [non regression]', () => {
${0} | ${2 ** 3 - 2} | ${"range of size divisor of mersenne's one minus one"}
${0} | ${2 ** 3} | ${"range of size divisor of mersenne's one plus one"}
${48} | ${69} | ${'random range'}
${MERSENNE_MIN} | ${MERSENNE_MAX} | ${"mersenne's range"}
${MERSENNE_MIN} | ${MERSENNE_MAX - 1} | ${"mersenne's range minus one"}
${MERSENNE_MIN} | ${MERSENNE_MAX + 1} | ${"mersenne's range plus one"}
${-0x80000000} | ${0x7fffffff} | ${"mersenne's range"}
${-0x80000000} | ${0x7fffffff - 1} | ${"mersenne's range minus one"}
${-0x80000000} | ${0x7fffffff + 1} | ${"mersenne's range plus one"}
${0} | ${2 ** 40 - 1} | ${"range of size multiple of mersenne's one"}
${0} | ${2 ** 40 - 2} | ${"range of size multiple of mersenne's one minus one"}
${0} | ${2 ** 40} | ${"range of size multiple of mersenne's one plus one"}
Expand Down
7 changes: 2 additions & 5 deletions test/unit/distribution/UniformBigIntDistribution.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ import { uniformBigIntDistribution } from '../../../src/distribution/UniformBigI
import { uniformIntDistribution } from '../../../src/distribution/UniformIntDistribution';
import mersenne from '../../../src/generator/MersenneTwister';

const MERSENNE_MIN = mersenne(0).min();
const MERSENNE_MAX = mersenne(0).max();

const bigIntArbitrary = fc
.tuple(fc.boolean(), fc.nat(0xffffffff), fc.nat(0xffffffff), fc.nat(0xffffffff), fc.nat(0xffffffff))
.map(([sign, a3, a2, a1, a0]) => {
Expand Down Expand Up @@ -38,8 +35,8 @@ describe('uniformBigIntDistribution', () => {
fc.assert(
fc.property(
fc.integer().noShrink(),
fc.integer({ min: MERSENNE_MIN, max: MERSENNE_MAX }),
fc.integer({ min: MERSENNE_MIN, max: MERSENNE_MAX }),
fc.integer({ min: -0x80000000, max: 0x7fffffff }),
fc.integer({ min: -0x80000000, max: 0x7fffffff }),
(seed, a, b) => {
const minV = a < b ? a : b;
const maxV = a < b ? b : a;
Expand Down
9 changes: 3 additions & 6 deletions test/unit/distribution/UniformIntDistribution.noreg.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,16 @@ import { uniformIntDistribution } from '../../../src/distribution/UniformIntDist
import mersenne from '../../../src/generator/MersenneTwister';
import { RandomGenerator } from '../../../src/generator/RandomGenerator';

const MERSENNE_MIN = mersenne(0).min();
const MERSENNE_MAX = mersenne(0).max();

describe('uniformIntDistribution [non regression]', () => {
it.each`
from | to | topic
${0} | ${2 ** 3 - 1} | ${"range of size divisor of mersenne's one"}
${0} | ${2 ** 3 - 2} | ${"range of size divisor of mersenne's one minus one"}
${0} | ${2 ** 3} | ${"range of size divisor of mersenne's one plus one"}
${48} | ${69} | ${'random range'}
${MERSENNE_MIN} | ${MERSENNE_MAX} | ${"mersenne's range"}
${MERSENNE_MIN} | ${MERSENNE_MAX - 1} | ${"mersenne's range minus one"}
${MERSENNE_MIN} | ${MERSENNE_MAX + 1} | ${"mersenne's range plus one"}
${-0x80000000} | ${0x7fffffff} | ${"mersenne's range"}
${-0x80000000} | ${0x7fffffff - 1} | ${"mersenne's range minus one"}
${-0x80000000} | ${0x7fffffff + 1} | ${"mersenne's range plus one"}
${0} | ${2 ** 40 - 1} | ${"range of size multiple of mersenne's one"}
${0} | ${2 ** 40 - 2} | ${"range of size multiple of mersenne's one minus one"}
${0} | ${2 ** 40} | ${"range of size multiple of mersenne's one plus one"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { RandomGenerator } from '../../../../src/generator/RandomGenerator';

class NatGenerator implements RandomGenerator {
constructor(private current: number) {
this.current = current % 0x80000000;
this.current = current | 0;
}
clone(): RandomGenerator {
return new NatGenerator(this.current);
Expand All @@ -17,15 +17,9 @@ class NatGenerator implements RandomGenerator {
}
unsafeNext(): number {
const previousCurrent = this.current;
this.current = (this.current + 1) % 0x80000000;
this.current = (this.current + 1) | 0;
return previousCurrent;
}
min(): number {
return 0;
}
max(): number {
return 0x7fffffff;
}
}

class ModNatGenerator implements RandomGenerator {
Expand All @@ -42,12 +36,6 @@ class ModNatGenerator implements RandomGenerator {
this.current = nrng;
return Math.abs(v % this.mod);
}
min(): number {
return 0;
}
max(): number {
return this.mod - 1;
}
}

const MAX_RANGE: number = 1000;
Expand Down Expand Up @@ -96,24 +84,10 @@ describe('unsafeUniformIntDistributionInternal', () => {
}
)
));
it('Should be able to generate values larger than the RandomGenerator', () =>
fc.assert(
fc.property(fc.integer(), fc.integer({ min: 2, max: 0x7fffffff }), (seed, mod) => {
let rng: RandomGenerator = new ModNatGenerator(mersenne(seed), mod);
expect(rng.max() - rng.min() + 1).toBeLessThan(Number.MAX_SAFE_INTEGER);

for (let numTries = 0; numTries < 100; ++numTries) {
const v = unsafeUniformIntDistributionInternal(Number.MAX_SAFE_INTEGER, rng);
if (v > rng.max()) {
return true;
}
}
return false;
})
));
it('Should be able to generate values outside bitwise operations', () =>
it('Should be able to generate values larger than the RandomGenerator and outside bitwise operations', () =>
fc.assert(
fc.property(fc.integer(), fc.integer({ min: 2, max: 0x7fffffff }), (seed, mod) => {
// kept it for legacy reasons, but could probably go without it
let rng: RandomGenerator = new ModNatGenerator(mersenne(seed), mod);
for (let numTries = 0; numTries < 100; ++numTries) {
const v = unsafeUniformIntDistributionInternal(Number.MAX_SAFE_INTEGER, rng);
Expand Down
4 changes: 2 additions & 2 deletions test/unit/generator/RandomGenerator.properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ export function valuesInRange(rng_for: (seed: number) => RandomGenerator) {
return fc.property(fc.integer(), fc.nat(MAX_SIZE), (seed, offset) => {
const rng = rng_for(seed);
const value = skipN(rng, offset).next()[0];
assert.ok(value >= rng.min());
assert.ok(value <= rng.max());
assert.ok(value >= -0x80000000);
assert.ok(value <= 0x7fffffff);
});
}

Expand Down

0 comments on commit c45912f

Please sign in to comment.