diff options
| author | Shinigami <[email protected]> | 2022-10-30 09:03:26 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-10-30 08:03:26 +0000 |
| commit | 9abfcfb90cc73bfe961d446f5fb950976d5c2ccd (patch) | |
| tree | 1e76203a6cfd8bb6c2b83734fee4cebf56db5072 | |
| parent | 4da3d5eac3805f609f4e40385aee6a645fd93540 (diff) | |
| download | faker-9abfcfb90cc73bfe961d446f5fb950976d5c2ccd.tar.xz faker-9abfcfb90cc73bfe961d446f5fb950976d5c2ccd.zip | |
refactor(mersenne): rewrite internal mersenne (#1447)
| -rw-r--r-- | src/faker.ts | 11 | ||||
| -rw-r--r-- | src/internal/mersenne/mersenne.ts | 96 | ||||
| -rw-r--r-- | src/modules/datatype/index.ts | 11 | ||||
| -rw-r--r-- | test/__snapshots__/mersenne.spec.ts.snap | 48 | ||||
| -rw-r--r-- | test/mersenne.spec.ts | 75 |
5 files changed, 75 insertions, 166 deletions
diff --git a/src/faker.ts b/src/faker.ts index f8a46511..fd7ed522 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -1,7 +1,8 @@ import type { LocaleDefinition } from './definitions'; import { FakerError } from './errors/faker-error'; import { deprecated } from './internal/deprecated'; -import { MersenneModule } from './internal/mersenne/mersenne'; +import type { Mersenne } from './internal/mersenne/mersenne'; +import mersenne from './internal/mersenne/mersenne'; import type { KnownLocale } from './locales'; import { AnimalModule } from './modules/animal'; import { ColorModule } from './modules/color'; @@ -79,7 +80,7 @@ export class Faker { readonly definitions: LocaleDefinition = this.initDefinitions(); /** @internal */ - private readonly _mersenne: MersenneModule = new MersenneModule(); + private readonly _mersenne: Mersenne = mersenne(); readonly random: RandomModule = new RandomModule(this); @@ -286,11 +287,7 @@ export class Faker { seed( seed: number | number[] = Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER) ): number | number[] { - if (Array.isArray(seed) && seed.length) { - this._mersenne.seed_array(seed); - } else if (!Array.isArray(seed) && !isNaN(seed)) { - this._mersenne.seed(seed); - } + this._mersenne.seed(seed); return seed; } diff --git a/src/internal/mersenne/mersenne.ts b/src/internal/mersenne/mersenne.ts index 16019e29..e7c0355c 100644 --- a/src/internal/mersenne/mersenne.ts +++ b/src/internal/mersenne/mersenne.ts @@ -1,81 +1,49 @@ -import { FakerError } from '../../errors/faker-error'; -import Gen from './twister'; +import Twister from './twister'; /** - * Module to generate seed based random numbers. + * Generate seed based random numbers. * * @internal */ -export class MersenneModule { - private gen = new Gen(); - - constructor() { - this.gen.initGenrand(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)); - - // Bind `this` so namespaced is working correctly - for (const name of Object.getOwnPropertyNames(MersenneModule.prototype)) { - if (name === 'constructor' || typeof this[name] !== 'function') { - continue; - } - this[name] = this[name].bind(this); - } - } - +export interface Mersenne { /** - * Generates a random number between `[min, max)`. - * - * @param max The maximum number. Defaults to `32768`. - * @param min The minimum number. Defaults to `0`. + * Generates a random number between `[min, max)`. The result is already floored. * - * @example - * faker.mersenne.rand() // 15515 - * faker.mersenne.rand(1000, 500) // 578 - * - * @since 5.5.0 + * @param options The options to generate a random number. + * @param options.min The minimum number. + * @param options.max The maximum number. */ - rand(max = 32768, min = 0): number { - if (min > max) { - const temp = min; - min = max; - max = temp; - } - - return Math.floor(this.gen.genrandReal2() * (max - min) + min); - } + next(options: { max: number; min: number }): number; /** * Sets the seed to use. * - * @param S The seed to use. - * @throws If the seed is not a `number`. - * - * @since 5.5.0 + * @param seed The seed to use. */ - seed(S: number): void { - if (typeof S !== 'number') { - throw new FakerError( - `seed(S) must take numeric argument; is ${typeof S}` - ); - } + seed(seed: number | number[]): void; +} + +/** + * Generate seed based random numbers. + * + * @internal + */ +export default function mersenne(): Mersenne { + const twister = new Twister(); - this.gen.initGenrand(S); - } + twister.initGenrand(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)); - /** - * Sets the seed to use. - * - * @param A The seed to use. - * @throws If the seed is not a `number[]`. - * - * @since 5.5.0 - */ - seed_array(A: number[]): void { - if (typeof A !== 'object') { - throw new FakerError( - `seed_array(A) must take array of numbers; is ${typeof A}` - ); - } + return { + next({ min, max }): number { + return Math.floor(twister.genrandReal2() * (max - min) + min); + }, - this.gen.initByArray(A, A.length); - } + seed(seed: number | number[]): void { + if (typeof seed === 'number') { + twister.initGenrand(seed); + } else if (Array.isArray(seed)) { + twister.initByArray(seed, seed.length); + } + }, + }; } diff --git a/src/modules/datatype/index.ts b/src/modules/datatype/index.ts index 41c5c3e4..d4fcbbd6 100644 --- a/src/modules/datatype/index.ts +++ b/src/modules/datatype/index.ts @@ -1,7 +1,7 @@ import type { Faker } from '../..'; import { FakerError } from '../../errors/faker-error'; import { deprecated } from '../../internal/deprecated'; -import type { MersenneModule } from '../../internal/mersenne/mersenne'; +import type { Mersenne } from '../../internal/mersenne/mersenne'; /** * Module to generate various primitive values and data types. @@ -56,13 +56,14 @@ export class DatatypeModule { throw new FakerError(`Max ${max} should be greater than min ${min}.`); } - const mersenne: MersenneModule = + const mersenne: Mersenne = // @ts-expect-error: access private member field this.faker._mersenne; - const randomNumber = Math.floor( - mersenne.rand(max / precision + 1, min / precision) - ); + const randomNumber = mersenne.next({ + min: min / precision, + max: max / precision + 1, + }); // Workaround problem in float point arithmetics for e.g. 6681493 / 0.01 return randomNumber / (1 / precision); diff --git a/test/__snapshots__/mersenne.spec.ts.snap b/test/__snapshots__/mersenne.spec.ts.snap index 1c221b19..21d3e4cc 100644 --- a/test/__snapshots__/mersenne.spec.ts.snap +++ b/test/__snapshots__/mersenne.spec.ts.snap @@ -1,49 +1,37 @@ // Vitest Snapshot v1 -exports[`mersenne twister > seed: [42,1,2] > rand() 1`] = `28056`; +exports[`mersenne twister > seed: [42,1,2] > should return deterministic values for next({ min: -50, max: 60 }) 1`] = `44`; -exports[`mersenne twister > seed: [42,1,2] > should return deterministic values for rand(100, 0) 1`] = `85`; +exports[`mersenne twister > seed: [42,1,2] > should return deterministic values for next({ min: -60, max: 0 }) 1`] = `-9`; -exports[`mersenne twister > seed: [42,1,2] > should return deterministic values for rand(100, undefined) 1`] = `85`; +exports[`mersenne twister > seed: [42,1,2] > should return deterministic values for next({ min: 0, max: 100 }) 1`] = `85`; -exports[`mersenne twister > seed: [42,1,2] > should return deterministic values for rand(undefined, 0) 1`] = `28056`; +exports[`mersenne twister > seed: [1211,1,2] > should return deterministic values for next({ min: -50, max: 60 }) 1`] = `48`; -exports[`mersenne twister > seed: [1211,1,2] > rand() 1`] = `29217`; +exports[`mersenne twister > seed: [1211,1,2] > should return deterministic values for next({ min: -60, max: 0 }) 1`] = `-7`; -exports[`mersenne twister > seed: [1211,1,2] > should return deterministic values for rand(100, 0) 1`] = `89`; +exports[`mersenne twister > seed: [1211,1,2] > should return deterministic values for next({ min: 0, max: 100 }) 1`] = `89`; -exports[`mersenne twister > seed: [1211,1,2] > should return deterministic values for rand(100, undefined) 1`] = `89`; +exports[`mersenne twister > seed: [1337,1,2] > should return deterministic values for next({ min: -50, max: 60 }) 1`] = `-31`; -exports[`mersenne twister > seed: [1211,1,2] > should return deterministic values for rand(undefined, 0) 1`] = `29217`; +exports[`mersenne twister > seed: [1337,1,2] > should return deterministic values for next({ min: -60, max: 0 }) 1`] = `-50`; -exports[`mersenne twister > seed: [1337,1,2] > rand() 1`] = `5895`; +exports[`mersenne twister > seed: [1337,1,2] > should return deterministic values for next({ min: 0, max: 100 }) 1`] = `17`; -exports[`mersenne twister > seed: [1337,1,2] > should return deterministic values for rand(100, 0) 1`] = `17`; +exports[`mersenne twister > seed: 42 > should return deterministic values for next({ min: -50, max: 60 }) 1`] = `-9`; -exports[`mersenne twister > seed: [1337,1,2] > should return deterministic values for rand(100, undefined) 1`] = `17`; +exports[`mersenne twister > seed: 42 > should return deterministic values for next({ min: -60, max: 0 }) 1`] = `-38`; -exports[`mersenne twister > seed: [1337,1,2] > should return deterministic values for rand(undefined, 0) 1`] = `5895`; +exports[`mersenne twister > seed: 42 > should return deterministic values for next({ min: 0, max: 100 }) 1`] = `37`; -exports[`mersenne twister > seed: 42 > rand() 1`] = `12272`; +exports[`mersenne twister > seed: 1211 > should return deterministic values for next({ min: -50, max: 60 }) 1`] = `52`; -exports[`mersenne twister > seed: 42 > should return deterministic values for rand(100, 0) 1`] = `37`; +exports[`mersenne twister > seed: 1211 > should return deterministic values for next({ min: -60, max: 0 }) 1`] = `-5`; -exports[`mersenne twister > seed: 42 > should return deterministic values for rand(100, undefined) 1`] = `37`; +exports[`mersenne twister > seed: 1211 > should return deterministic values for next({ min: 0, max: 100 }) 1`] = `92`; -exports[`mersenne twister > seed: 42 > should return deterministic values for rand(undefined, 0) 1`] = `12272`; +exports[`mersenne twister > seed: 1337 > should return deterministic values for next({ min: -50, max: 60 }) 1`] = `-22`; -exports[`mersenne twister > seed: 1211 > rand() 1`] = `30425`; +exports[`mersenne twister > seed: 1337 > should return deterministic values for next({ min: -60, max: 0 }) 1`] = `-45`; -exports[`mersenne twister > seed: 1211 > should return deterministic values for rand(100, 0) 1`] = `92`; - -exports[`mersenne twister > seed: 1211 > should return deterministic values for rand(100, undefined) 1`] = `92`; - -exports[`mersenne twister > seed: 1211 > should return deterministic values for rand(undefined, 0) 1`] = `30425`; - -exports[`mersenne twister > seed: 1337 > rand() 1`] = `8586`; - -exports[`mersenne twister > seed: 1337 > should return deterministic values for rand(100, 0) 1`] = `26`; - -exports[`mersenne twister > seed: 1337 > should return deterministic values for rand(100, undefined) 1`] = `26`; - -exports[`mersenne twister > seed: 1337 > should return deterministic values for rand(undefined, 0) 1`] = `8586`; +exports[`mersenne twister > seed: 1337 > should return deterministic values for next({ min: 0, max: 100 }) 1`] = `26`; diff --git a/test/mersenne.spec.ts b/test/mersenne.spec.ts index 6b0a3125..f6585783 100644 --- a/test/mersenne.spec.ts +++ b/test/mersenne.spec.ts @@ -1,53 +1,39 @@ import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; -import { FakerError } from '../src/errors/faker-error'; -import { MersenneModule } from '../src/internal/mersenne/mersenne'; +import type { Mersenne } from '../src/internal/mersenne/mersenne'; +import mersenneFn from '../src/internal/mersenne/mersenne'; import { seededRuns } from './support/seededRuns'; const minMaxTestCases = [ - { max: 100, min: 0 }, - { max: undefined, min: 0 }, - { max: 100, min: undefined }, + { min: 0, max: 100 }, + { min: -60, max: 0 }, + { min: -50, max: 60 }, ]; -const functionNames = ['rand']; - const NON_SEEDED_BASED_RUN = 25; describe('mersenne twister', () => { - let mersenne: MersenneModule; + let mersenne: Mersenne; beforeEach(() => { - mersenne = new MersenneModule(); + mersenne = mersenneFn(); }); for (const seed of [...seededRuns, [42, 1, 2], [1337, 1, 2], [1211, 1, 2]]) { describe(`seed: ${JSON.stringify(seed)}`, () => { beforeEach(() => { - if (Array.isArray(seed)) { - mersenne.seed_array(seed); - } else { - mersenne.seed(seed); - } + mersenne.seed(seed); }); - for (const functionName of functionNames) { - it(`${functionName}()`, () => { - const actual = mersenne[functionName](); - - expect(actual).toMatchSnapshot(); - }); - } - for (const { min, max } of minMaxTestCases) { - it(`should return deterministic values for rand(${max}, ${min})`, () => { - const actual = mersenne.rand(max, min); + it(`should return deterministic values for next({ min: ${min}, max: ${max} })`, () => { + const actual = mersenne.next({ min, max }); expect(actual).toMatchSnapshot(); }); } - it.todo(`should return 0 for rand(1)`, () => { - const actual = mersenne.rand(1); + it.todo(`should return 0 for next({ min: ${0}, max: ${1} })`, () => { + const actual = mersenne.next({ min: 0, max: 1 }); expect(actual).toEqual(0); }); @@ -66,22 +52,13 @@ describe('mersenne twister', () => { for (const seed of seeds) { describe(`random seeded tests ${JSON.stringify(seed)}`, () => { beforeAll(() => { - if (Array.isArray(seed)) { - mersenne.seed_array(seed); - } else { - mersenne.seed(seed); - } + mersenne.seed(seed); }); for (let i = 1; i <= NON_SEEDED_BASED_RUN; i++) { - describe('rand', () => { - it('should return a random number without given min / max arguments', () => { - const randomNumber = mersenne.rand(); - expect(randomNumber).toBeTypeOf('number'); - }); - + describe('next', () => { it('should return random number from interval [min, max)', () => { - const actual = mersenne.rand(0, 2); + const actual = mersenne.next({ min: 0, max: 2 }); expect(actual).toBeGreaterThanOrEqual(0); expect(actual).toBeLessThan(2); @@ -90,26 +67,4 @@ describe('mersenne twister', () => { } }); } - - it('should throw an error when attempting to seed() a non-integer', () => { - expect(() => - mersenne.seed( - // @ts-expect-error: non-integer error - 'abc' - ) - ).toThrowError( - new FakerError('seed(S) must take numeric argument; is string') - ); - }); - - it('should throw an error when attempting to seed() a non-integer', () => { - expect(() => - mersenne.seed_array( - // @ts-expect-error: non-integer error - 'abc' - ) - ).toThrowError( - new FakerError('seed_array(A) must take array of numbers; is string') - ); - }); }); |
