Skip to content

Commit

Permalink
♻️ Extract internal logic of uniformIntDistribution into shared inter…
Browse files Browse the repository at this point in the history
…nals (#116)
  • Loading branch information
dubzzz authored Oct 26, 2020
1 parent 670e2ab commit c55f987
Show file tree
Hide file tree
Showing 9 changed files with 381 additions and 228 deletions.
53 changes: 8 additions & 45 deletions src/distribution/UniformIntDistribution.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,11 @@
import Distribution from './Distribution';
import RandomGenerator from '../generator/RandomGenerator';
import { uniformIntDistributionInternal } from './internals/UniformIntDistributionInternal';

function uniformIntInternal(from: number, diff: number, rng: RandomGenerator): [number, RandomGenerator] {
const MinRng = rng.min();
const NumValues = rng.max() - rng.min() + 1;

// Range provided by the RandomGenerator is large enough
if (diff <= NumValues) {
let nrng = rng;
const MaxAllowed = NumValues - (NumValues % diff);
while (true) {
const out = nrng.next();
const deltaV = out[0] - MinRng;
nrng = out[1];
if (deltaV < MaxAllowed) {
return [(deltaV % diff) + from, nrng]; // Warning: we expect NumValues <= 2**32, so diff too
}
}
}

// Compute number of iterations required to have enough random
// to build uniform entries in the asked range
let FinalNumValues = NumValues * NumValues;
let NumIterations = 2; // At least 2 (at this point in the code)
while (FinalNumValues < diff) {
FinalNumValues *= NumValues;
++NumIterations;
}
const MaxAcceptedRandom = diff * Math.floor((1 * FinalNumValues) / diff);

let nrng = rng;
while (true) {
// Aggregate mutiple calls to next() into a single random value
let value = 0;
for (let num = 0; num !== NumIterations; ++num) {
const out = nrng.next();
value = NumValues * value + (out[0] - MinRng); // Warning: we overflow may when diff > max_safe (eg: =max_safe-min_safe+1)
nrng = out[1];
}
if (value < MaxAcceptedRandom) {
const inDiff = value - diff * Math.floor((1 * value) / diff);
return [inDiff + from, nrng];
}
}
function uniformIntInternal(from: number, rangeSize: number, rng: RandomGenerator): [number, RandomGenerator] {
const g = uniformIntDistributionInternal(rangeSize, rng);
g[0] += from;
return g;
}

/**
Expand All @@ -65,12 +28,12 @@ function uniformIntDistribution(from: number, to: number): Distribution<number>;
*/
function uniformIntDistribution(from: number, to: number, rng: RandomGenerator): [number, RandomGenerator];
function uniformIntDistribution(from: number, to: number, rng?: RandomGenerator) {
const diff = to - from + 1;
const rangeSize = to - from + 1;
if (rng != null) {
return uniformIntInternal(from, diff, rng);
return uniformIntInternal(from, rangeSize, rng);
}
return function (rng: RandomGenerator) {
return uniformIntInternal(from, diff, rng);
return uniformIntInternal(from, rangeSize, rng);
};
}

Expand Down
49 changes: 49 additions & 0 deletions src/distribution/internals/UniformIntDistributionInternal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import RandomGenerator from '../../generator/RandomGenerator';

/**
* Uniformly generate number in range [0 ; rangeSize[
* @internal
*/
export function uniformIntDistributionInternal(rangeSize: number, rng: RandomGenerator): [number, RandomGenerator] {
const MinRng = rng.min();
const NumValues = rng.max() - rng.min() + 1;

// Range provided by the RandomGenerator is large enough
if (rangeSize <= NumValues) {
let nrng = rng;
const MaxAllowed = NumValues - (NumValues % rangeSize);
while (true) {
const out = nrng.next();
const deltaV = out[0] - MinRng;
nrng = out[1];
if (deltaV < MaxAllowed) {
return [deltaV % rangeSize, nrng]; // Warning: we expect NumValues <= 2**32, so diff too
}
}
}

// Compute number of iterations required to have enough random
// to build uniform entries in the asked range
let FinalNumValues = NumValues * NumValues;
let NumIterations = 2; // At least 2 (at this point in the code)
while (FinalNumValues < rangeSize) {
FinalNumValues *= NumValues;
++NumIterations;
}
const MaxAcceptedRandom = rangeSize * Math.floor((1 * FinalNumValues) / rangeSize);

let nrng = rng;
while (true) {
// Aggregate mutiple calls to next() into a single random value
let value = 0;
for (let num = 0; num !== NumIterations; ++num) {
const out = nrng.next();
value = NumValues * value + (out[0] - MinRng); // Warning: we overflow may when diff > max_safe (eg: =max_safe-min_safe+1)
nrng = out[1];
}
if (value < MaxAcceptedRandom) {
const inDiff = value - rangeSize * Math.floor((1 * value) / rangeSize);
return [inDiff, nrng];
}
}
}
48 changes: 48 additions & 0 deletions test/unit/distribution/UniformBigIntDistribution.noreg.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { uniformBigIntDistribution } from '../../../src/distribution/UniformBigIntDistribution';
import mersenne from '../../../src/generator/MersenneTwister';

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

describe('uniformBigIntDistribution [non regression]', () => {
if (typeof BigInt === 'undefined') {
it('no test', () => {
expect(true).toBe(true);
});
return;
}
if (typeof BigInt !== 'undefined') {
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"}
${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"}
${Number.MIN_SAFE_INTEGER} | ${Number.MAX_SAFE_INTEGER} | ${'full integer range'}
`('Should not change its output in range ($from, $to) except for major bumps', ({ from, to }) => {
// Remark:
// ========================
// This test is purely there to ensure that we do not introduce any regression
// during a commit without noticing it.
// The values we expect in the output are just a snapshot taken at a certain time
// in the past. They might be wrong values with bugs.

let rng = mersenne(0);
const distribution = uniformBigIntDistribution(BigInt(from), BigInt(to));

const values: bigint[] = [];
for (let idx = 0; idx !== 10; ++idx) {
const [v, nrng] = distribution(rng);
values.push(v);
rng = nrng;
}
expect(values).toMatchSnapshot();
});
}
});
32 changes: 0 additions & 32 deletions test/unit/distribution/UniformBigIntDistribution.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,37 +50,5 @@ describe('uniformBigIntDistribution', () => {
}
)
));
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"}
${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"}
${Number.MIN_SAFE_INTEGER} | ${Number.MAX_SAFE_INTEGER} | ${'full integer range'}
`('Should not change its output in range ($from, $to) except for major bumps', ({ from, to }) => {
// Remark:
// ========================
// This test is purely there to ensure that we do not introduce any regression
// during a commit without noticing it.
// The values we expect in the output are just a snapshot taken at a certain time
// in the past. They might be wrong values with bugs.

let rng = mersenne(0);
const distribution = uniformBigIntDistribution(BigInt(from), BigInt(to));

const values: bigint[] = [];
for (let idx = 0; idx !== 10; ++idx) {
const [v, nrng] = distribution(rng);
values.push(v);
rng = nrng;
}
expect(values).toMatchSnapshot();
});
}
});
40 changes: 40 additions & 0 deletions test/unit/distribution/UniformIntDistribution.noreg.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { uniformIntDistribution } from '../../../src/distribution/UniformIntDistribution';
import mersenne from '../../../src/generator/MersenneTwister';

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"}
${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"}
${Number.MIN_SAFE_INTEGER} | ${Number.MAX_SAFE_INTEGER} | ${'full integer range'}
`('Should not change its output in range ($from, $to) except for major bumps', ({ from, to }) => {
// Remark:
// ========================
// This test is purely there to ensure that we do not introduce any regression
// during a commit without noticing it.
// The values we expect in the output are just a snapshot taken at a certain time
// in the past. They might be wrong values with bugs.

let rng = mersenne(0);
const distribution = uniformIntDistribution(from, to);

const values: number[] = [];
for (let idx = 0; idx !== 10; ++idx) {
const [v, nrng] = distribution(rng);
values.push(v);
rng = nrng;
}
expect(values).toMatchSnapshot();
});
});
Loading

0 comments on commit c55f987

Please sign in to comment.