-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
♻️ Extract internal logic of uniformIntDistribution into shared inter…
…nals (#116)
- Loading branch information
Showing
9 changed files
with
381 additions
and
228 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
src/distribution/internals/UniformIntDistributionInternal.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
48
test/unit/distribution/UniformBigIntDistribution.noreg.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
test/unit/distribution/UniformIntDistribution.noreg.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); | ||
}); |
Oops, something went wrong.