Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️ Extract internal logic of uniformIntDistribution into shared internals #116

Merged
merged 4 commits into from
Oct 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 8 additions & 45 deletions src/distribution/UniformIntDistribution.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,22 @@
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;
}

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