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

test(mersenne): add tests for value ranges #2470

Merged
merged 21 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e22a424
test(mersenne): add tests for value ranges
ST-DDT Oct 11, 2023
f0c38cb
chore: slight improvements
ST-DDT Oct 11, 2023
13bf7fb
chore: remove obsolete snap file
ST-DDT Oct 11, 2023
23bc03d
Merge branch 'next' into test/mersenne-twister/value-range
ST-DDT Oct 12, 2023
0e5cfdc
chore: apply review suggestions
ST-DDT Oct 15, 2023
4cc2f79
chore: apply review suggestions
ST-DDT Oct 15, 2023
a65723e
docs: improve jsdocs
ST-DDT Oct 15, 2023
ed60992
Merge branch 'next' into test/mersenne-twister/value-range
ST-DDT Oct 15, 2023
31cdbb9
Merge branch 'next' into test/mersenne-twister/value-range
ST-DDT Oct 15, 2023
ed3e946
Merge branch 'next' into test/mersenne-twister/value-range
ST-DDT Oct 20, 2023
2a93680
Merge branch 'next' into test/mersenne-twister/value-range
ST-DDT Oct 22, 2023
15a356e
Merge branch 'next' into test/mersenne-twister/value-range
ST-DDT Oct 26, 2023
75e3bb9
Merge branch 'next' into test/mersenne-twister/value-range
ST-DDT Oct 29, 2023
f5414fb
Merge branch 'next' into test/mersenne-twister/value-range
ST-DDT Oct 30, 2023
4da0ede
Merge branch 'next' into test/mersenne-twister/value-range
ST-DDT Nov 6, 2023
212cc79
Merge branch 'next' into test/mersenne-twister/value-range
ST-DDT Nov 7, 2023
15a8549
Merge branch 'next' into test/mersenne-twister/value-range
ST-DDT Nov 7, 2023
60cb0f3
Merge branch 'next' into test/mersenne-twister/value-range
ST-DDT Nov 13, 2023
d4db6df
Merge branch 'next' into test/mersenne-twister/value-range
ST-DDT Nov 14, 2023
fe32691
Merge branch 'next' into test/mersenne-twister/value-range
ST-DDT Nov 25, 2023
0c720dd
Merge branch 'next' into test/mersenne-twister/value-range
ST-DDT Dec 1, 2023
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
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
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);
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
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);
});
});
});
});