Skip to content

Commit

Permalink
馃敟 Clean internals of uniform distribution (#515)
Browse files Browse the repository at this point in the history
  • Loading branch information
dubzzz committed Jan 9, 2023
1 parent 520cca7 commit a7e19a8
Show file tree
Hide file tree
Showing 2 changed files with 8 additions and 68 deletions.
45 changes: 8 additions & 37 deletions src/distribution/internals/UnsafeUniformIntDistributionInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,19 @@ import { RandomGenerator } from '../../generator/RandomGenerator';

/**
* Uniformly generate number in range [0 ; rangeSize[
* With rangeSize <= 0x100000000
* @internal
*/
export function unsafeUniformIntDistributionInternal(rangeSize: number, rng: RandomGenerator): number {
const MinRng = -0x80000000;
const NumValues = 0x100000000;

// Range provided by the RandomGenerator is large enough
if (rangeSize <= NumValues) {
const MaxAllowed = NumValues - (NumValues % rangeSize);
let deltaV = rng.unsafeNext() - MinRng;
while (deltaV >= MaxAllowed) {
deltaV = rng.unsafeNext() - MinRng;
}
return deltaV % rangeSize; // Warning: we expect NumValues <= 2**32, so diff too
// Range provided by the RandomGenerator is large enough,
// given rangeSize <= 0x100000000 and RandomGenerator is uniform on 0x100000000 values
const MaxAllowed = NumValues - (NumValues % rangeSize);
let deltaV = rng.unsafeNext() - MinRng;
while (deltaV >= MaxAllowed) {
deltaV = rng.unsafeNext() - MinRng;
}

// 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 largeDeltaV = unsafeComputeValue(rng, NumIterations, NumValues, MinRng);
while (largeDeltaV >= MaxAcceptedRandom) {
largeDeltaV = unsafeComputeValue(rng, NumIterations, NumValues, MinRng);
}
const inDiff = largeDeltaV - rangeSize * Math.floor((1 * largeDeltaV) / rangeSize);
return inDiff;
}

/**
* Aggregate multiple calls to next() into a single random value
*/
function unsafeComputeValue(rng: RandomGenerator, NumIterations: number, NumValues: number, MinRng: number): number {
let value = 0;
for (let num = 0; num !== NumIterations; ++num) {
const out = rng.unsafeNext();
value = NumValues * value + (out - MinRng); // Warning: we overflow may when diff > max_safe (eg: =max_safe-min_safe+1)
}
return value;
return deltaV % rangeSize; // Warning: we expect NumValues <= 2**32, so diff too
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as fc from 'fast-check';

import { unsafeUniformIntDistributionInternal } from '../../../../src/distribution/internals/UnsafeUniformIntDistributionInternal';
import mersenne from '../../../../src/generator/MersenneTwister';
import { RandomGenerator } from '../../../../src/generator/RandomGenerator';

class NatGenerator implements RandomGenerator {
Expand All @@ -22,22 +21,6 @@ class NatGenerator implements RandomGenerator {
}
}

class ModNatGenerator implements RandomGenerator {
constructor(private current: RandomGenerator, private mod: number) {}
clone(): RandomGenerator {
return new ModNatGenerator(this.current, this.mod);
}
next(): [number, RandomGenerator] {
const nextRng = this.clone();
return [nextRng.unsafeNext(), nextRng];
}
unsafeNext(): number {
const [v, nrng] = this.current.next();
this.current = nrng;
return Math.abs(v % this.mod);
}
}

const MAX_RANGE: number = 1000;

describe('unsafeUniformIntDistributionInternal', () => {
Expand Down Expand Up @@ -84,18 +67,4 @@ describe('unsafeUniformIntDistributionInternal', () => {
}
)
));
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);
if (v > 0xffffffff) {
return true;
}
}
return false;
})
));
});

0 comments on commit a7e19a8

Please sign in to comment.