Skip to content

Commit

Permalink
test(mersenne): add tests for value ranges (#2470)
Browse files Browse the repository at this point in the history
  • Loading branch information
ST-DDT authored Dec 1, 2023
1 parent 5e0f4f7 commit 2eca5bc
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 59 deletions.
2 changes: 1 addition & 1 deletion src/internal/mersenne.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ import type { Randomizer } from '../randomizer';
*
* @internal
*/
class MersenneTwister19937 {
export class MersenneTwister19937 {
private readonly N = 624;
private readonly M = 397;
private readonly MATRIX_A = 0x9908b0df; // constant vector a
Expand Down
File renamed without changes.
17 changes: 17 additions & 0 deletions test/internal/mersenne-test-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Moved to a separate file to avoid importing the tests

/**
* The maximum value that can be returned by `MersenneTwister19937.genrandReal2()`.
* This is the max possible value with 32 bits of precision that is less than 1.
*/
export const TWISTER_32CO_MAX_VALUE = 0.9999999997671694;
/**
* The maximum value that can be returned by `MersenneTwister19937.genrandRes53()`.
* This is the max possible value with 53 bits of precision that is less than 1.
*/
export const TWISTER_53CO_MAX_VALUE = 0.9999999999999999;
// Re-exported because the value might change in the future
/**
* The maximum value that can be returned by `next()`.
*/
export const MERSENNE_MAX_VALUE = TWISTER_32CO_MAX_VALUE;
136 changes: 136 additions & 0 deletions test/internal/mersenne.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
import {
generateMersenne32Randomizer,
MersenneTwister19937,
} from '../../src/internal/mersenne';
import type { Randomizer } from '../../src/randomizer';
import { seededRuns } from '../support/seeded-runs';
import { times } from '../support/times';
import {
MERSENNE_MAX_VALUE,
TWISTER_32CO_MAX_VALUE,
TWISTER_53CO_MAX_VALUE,
} from './mersenne-test-utils';

const NON_SEEDED_BASED_RUN = 25;

function newTwister(
seed: number = Math.random() * Number.MAX_SAFE_INTEGER
): MersenneTwister19937 {
const twister = new MersenneTwister19937();
twister.initGenrand(seed);
return twister;
}

describe('MersenneTwister19937', () => {
describe('genrandInt32()', () => {
it('should be able to return 0', () => {
const twister = newTwister(257678572);

// There is no single value seed that can produce 0 in the first call
for (let i = 0; i < 5; i++) {
twister.genrandInt32();
}

const actual = twister.genrandInt32();
expect(actual).toBe(0);
});

it('should be able to return 2^32-1', () => {
const twister = newTwister(2855577693);
const actual = twister.genrandInt32();
expect(actual).toBe(2 ** 32 - 1);
});
});

describe('genrandReal2()', () => {
it('should be able to return 0', () => {
const twister = newTwister();
// shortcut to return minimal value
// the test above shows that it is possible to return 0
twister.genrandInt32 = () => 0;
const actual = twister.genrandReal2();
expect(actual).toBe(0);
});

it('should be able to return almost 1', () => {
const twister = newTwister();
// shortcut to return maximal value
// the test above shows that it is possible to return 2^32-1
twister.genrandInt32 = () => 2 ** 32 - 1;
const actual = twister.genrandReal2();
expect(actual).toBe(TWISTER_32CO_MAX_VALUE);
});
});

describe('genrandRes53()', () => {
it('should be able to return 0', () => {
const twister = newTwister();
// shortcut to return minimal value
// the test above shows that it is possible to return 0
twister.genrandInt32 = () => 0;
const actual = twister.genrandRes53();
expect(actual).toBe(0);
});

it('should be able to return almost 1', () => {
const twister = newTwister();
// shortcut to return maximal value
// the test above shows that it is possible to return 2^32-1
twister.genrandInt32 = () => 2 ** 32 - 1;
const actual = twister.genrandRes53();
expect(actual).toBe(TWISTER_53CO_MAX_VALUE);
});
});
});

describe('generateMersenne32Randomizer()', () => {
const randomizer: Randomizer = generateMersenne32Randomizer();

it('should return a result matching the interface', () => {
expect(randomizer).toBeDefined();
expect(randomizer).toBeTypeOf('object');
expect(randomizer.next).toBeTypeOf('function');
expect(randomizer.seed).toBeTypeOf('function');
});

describe.each(
[...seededRuns, ...seededRuns.map((v) => [v, 1, 2])].map((v) => [v])
)('seed: %j', (seed) => {
beforeEach(() => {
randomizer.seed(seed);
});

it('should return deterministic value for next()', () => {
const actual = randomizer.next();

expect(actual).toMatchSnapshot();
});
});

function randomSeed(): number {
return Math.ceil(Math.random() * 1_000_000_000);
}

// Create and log-back the seed for debug purposes
describe.each(
times(NON_SEEDED_BASED_RUN).flatMap(() => [
[randomSeed()],
[[randomSeed(), randomSeed()]],
])
)('random seeded tests %j', (seed) => {
beforeAll(() => {
randomizer.seed(seed);
});

describe('next', () => {
it('should return random number from interval [0, 1)', () => {
const actual = randomizer.next();

expect(actual).toBeGreaterThanOrEqual(0);
expect(actual).toBeLessThanOrEqual(MERSENNE_MAX_VALUE);
expect(actual).toBeLessThan(1);
});
});
});
});
57 changes: 0 additions & 57 deletions test/mersenne.spec.ts

This file was deleted.

37 changes: 36 additions & 1 deletion test/modules/number.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import validator from 'validator';
import { describe, expect, it } from 'vitest';
import { faker, FakerError } from '../../src';
import { faker, FakerError, SimpleFaker } from '../../src';
import { MERSENNE_MAX_VALUE } from '../internal/mersenne-test-utils';
import { seededTests } from '../support/seeded-runs';

describe('number', () => {
Expand Down Expand Up @@ -507,4 +508,38 @@ describe('number', () => {
});
});
});

describe('value range tests', () => {
const customFaker = new SimpleFaker();
// @ts-expect-error: access private member field
const randomizer = customFaker._randomizer;
describe('int', () => {
it('should be able to return 0', () => {
randomizer.next = () => 0;
const actual = customFaker.number.int();
expect(actual).toBe(0);
});

// TODO @ST-DDT 2023-10-12: This requires a randomizer with 53 bits of precision
it.todo('should be able to return MAX_SAFE_INTEGER', () => {
randomizer.next = () => MERSENNE_MAX_VALUE;
const actual = customFaker.number.int();
expect(actual).toBe(Number.MAX_SAFE_INTEGER);
});
});

describe('float', () => {
it('should be able to return 0', () => {
randomizer.next = () => 0;
const actual = customFaker.number.float();
expect(actual).toBe(0);
});

it('should be able to return almost 1', () => {
randomizer.next = () => MERSENNE_MAX_VALUE;
const actual = customFaker.number.float();
expect(actual).toBe(MERSENNE_MAX_VALUE);
});
});
});
});

0 comments on commit 2eca5bc

Please sign in to comment.