diff options
| author | ST-DDT <[email protected]> | 2023-10-04 18:13:00 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-10-04 18:13:00 +0200 |
| commit | 5410239245b4a6fe8c1976f8aa33c970923f9f40 (patch) | |
| tree | 7640a15dfc6781311082a6a7e7a87412d7abcf98 /src | |
| parent | 88b2443cab22ade0488643568896b64adef420b5 (diff) | |
| download | faker-5410239245b4a6fe8c1976f8aa33c970923f9f40.tar.xz faker-5410239245b4a6fe8c1976f8aa33c970923f9f40.zip | |
feat: support custom randomizer (#2284)
Diffstat (limited to 'src')
| -rw-r--r-- | src/faker.ts | 35 | ||||
| -rw-r--r-- | src/index.ts | 1 | ||||
| -rw-r--r-- | src/internal/mersenne.ts (renamed from src/internal/mersenne/twister.ts) | 28 | ||||
| -rw-r--r-- | src/internal/mersenne/mersenne.ts | 45 | ||||
| -rw-r--r-- | src/modules/number/index.ts | 12 | ||||
| -rw-r--r-- | src/randomizer.ts | 65 | ||||
| -rw-r--r-- | src/simple-faker.ts | 31 |
7 files changed, 156 insertions, 61 deletions
diff --git a/src/faker.ts b/src/faker.ts index 43811f05..e6b5889a 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -28,6 +28,7 @@ import { ScienceModule } from './modules/science'; import { SystemModule } from './modules/system'; import { VehicleModule } from './modules/vehicle'; import { WordModule } from './modules/word'; +import type { Randomizer } from './randomizer'; import { SimpleFaker } from './simple-faker'; import { mergeLocales } from './utils/merge-locales'; @@ -123,6 +124,10 @@ export class Faker extends SimpleFaker { * * @param options The options to use. * @param options.locale The locale data to use. + * @param options.randomizer The Randomizer to use. + * Specify this only if you want to use it to achieve a specific goal, + * such as sharing the same random generator with other instances/tools. + * Defaults to faker's Mersenne Twister based pseudo random number generator. * * @example * import { Faker, es } from '@faker-js/faker'; @@ -144,6 +149,15 @@ export class Faker extends SimpleFaker { * @see mergeLocales */ locale: LocaleDefinition | LocaleDefinition[]; + + /** + * The Randomizer to use. + * Specify this only if you want to use it to achieve a specific goal, + * such as sharing the same random generator with other instances/tools. + * + * @default generateMersenne32Randomizer() + */ + randomizer?: Randomizer; }); /** * Creates a new instance of Faker. @@ -180,6 +194,10 @@ export class Faker extends SimpleFaker { * @param options.locale The locale data to use or the name of the main locale. * @param options.locales The locale data to use. * @param options.localeFallback The name of the fallback locale to use. + * @param options.randomizer The Randomizer to use. + * Specify this only if you want to use it to achieve a specific goal, + * such as sharing the same random generator with other instances/tools. + * Defaults to faker's Mersenne Twister based pseudo random number generator. * * @example * import { Faker, es } from '@faker-js/faker'; @@ -203,6 +221,15 @@ export class Faker extends SimpleFaker { * @see mergeLocales */ locale: LocaleDefinition | LocaleDefinition[]; + + /** + * The Randomizer to use. + * Specify this only if you want to use it to achieve a specific goal, + * such as sharing the same random generator with other instances/tools. + * + * @default generateMersenne32Randomizer() + */ + randomizer?: Randomizer; } | { /** @@ -231,14 +258,18 @@ export class Faker extends SimpleFaker { ); constructor( options: - | { locale: LocaleDefinition | LocaleDefinition[] } + | { + locale: LocaleDefinition | LocaleDefinition[]; + randomizer?: Randomizer; + } | { locales: Record<string, LocaleDefinition>; locale?: string; localeFallback?: string; + randomizer?: Randomizer; } ) { - super(); + super({ randomizer: options.randomizer }); const { locales } = options as { locales: Record<string, LocaleDefinition>; diff --git a/src/index.ts b/src/index.ts index 000541b7..8adcd384 100644 --- a/src/index.ts +++ b/src/index.ts @@ -128,5 +128,6 @@ export type { StringModule } from './modules/string'; export type { SystemModule } from './modules/system'; export type { VehicleModule } from './modules/vehicle'; export type { WordModule } from './modules/word'; +export type { Randomizer } from './randomizer'; export { SimpleFaker, simpleFaker } from './simple-faker'; export { mergeLocales } from './utils/merge-locales'; diff --git a/src/internal/mersenne/twister.ts b/src/internal/mersenne.ts index d25e3acf..d97ec716 100644 --- a/src/internal/mersenne/twister.ts +++ b/src/internal/mersenne.ts @@ -1,3 +1,5 @@ +import type { Randomizer } from '../randomizer'; + /** * Copyright (c) 2022-2023 Faker * @@ -71,7 +73,7 @@ * * @internal */ -export default class MersenneTwister19937 { +class MersenneTwister19937 { private readonly N = 624; private readonly M = 397; private readonly MATRIX_A = 0x9908b0df; // constant vector a @@ -323,3 +325,27 @@ export default class MersenneTwister19937 { } // These real versions are due to Isaku Wada, 2002/01/09 } + +/** + * Generates a MersenneTwister19937 randomizer with 32 bits of precision. + * + * @internal + */ +export function generateMersenne32Randomizer(): Randomizer { + const twister = new MersenneTwister19937(); + + twister.initGenrand(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)); + + return { + next(): number { + return twister.genrandReal2(); + }, + 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/internal/mersenne/mersenne.ts b/src/internal/mersenne/mersenne.ts deleted file mode 100644 index c823af7e..00000000 --- a/src/internal/mersenne/mersenne.ts +++ /dev/null @@ -1,45 +0,0 @@ -import Twister from './twister'; - -/** - * Generate seed based random numbers. - * - * @internal - */ -export interface Mersenne { - /** - * Generates a random float between `[0, 1)`. - * This method is called `next` so that it could be used as an [iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol) - */ - next(): number; - - /** - * Sets the seed to use. - * - * @param seed The seed to use. - */ - seed(seed: number | number[]): void; -} - -/** - * Generate seed based random numbers. - * - * @internal - */ -export default function mersenne(): Mersenne { - const twister = new Twister(); - - twister.initGenrand(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)); - - return { - next(): number { - return twister.genrandReal2(); - }, - 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/number/index.ts b/src/modules/number/index.ts index a2e64724..9216df7a 100644 --- a/src/modules/number/index.ts +++ b/src/modules/number/index.ts @@ -1,7 +1,6 @@ import type { SimpleFaker } from '../..'; import { FakerError } from '../../errors/faker-error'; import { bindThisToMemberFunctions } from '../../internal/bind-this-to-member-functions'; -import type { Mersenne } from '../../internal/mersenne/mersenne'; /** * Module to generate numbers of any kind. @@ -83,10 +82,9 @@ export class NumberModule { throw new FakerError(`Max ${max} should be greater than min ${min}.`); } - const mersenne: Mersenne = - // @ts-expect-error: access private member field - this.faker._mersenne; - const real = mersenne.next(); + // @ts-expect-error: access private member field + const randomizer = this.faker._randomizer; + const real = randomizer.next(); return Math.floor(real * (effectiveMax + 1 - effectiveMin) + effectiveMin); } @@ -160,8 +158,8 @@ export class NumberModule { } // @ts-expect-error: access private member field - const mersenne: Mersenne = this.faker._mersenne; - const real = mersenne.next(); + const randomizer = this.faker._randomizer; + const real = randomizer.next(); return real * (max - min) + min; } diff --git a/src/randomizer.ts b/src/randomizer.ts new file mode 100644 index 00000000..3264ec1d --- /dev/null +++ b/src/randomizer.ts @@ -0,0 +1,65 @@ +/** + * Interface for a random number generator. + * + * **Note:** Normally there is no need to implement this interface directly, + * unless you want to achieve a specific goal with it. + * + * This interface enables you to use random generators from third party libraries such as [pure-rand](https://github.com/dubzzz/pure-rand). + * + * Instances are expected to be ready for use before being passed to any Faker constructor, + * this includes being `seed()`ed with either a random or fixed value. + * + * For more information please refer to the [documentation](/api/randomizer). + * + * @example + * import { Faker, Randomizer, SimpleFaker } from '@faker-js/faker'; + * import { RandomGenerator, xoroshiro128plus } from 'pure-rand'; + * + * function generatePureRandRandomizer( + * seed: number | number[] = Date.now() ^ (Math.random() * 0x100000000), + * factory: (seed: number) => RandomGenerator = xoroshiro128plus + * ): Randomizer { + * const self = { + * next: () => (self.generator.unsafeNext() >>> 0) / 0x100000000, + * seed: (seed: number | number[]) => { + * self.generator = factory(typeof seed === 'number' ? seed : seed[0]); + * }, + * } as Randomizer & { generator: RandomGenerator }; + * self.seed(seed); + * return self; + * } + * + * const randomizer = generatePureRandRandomizer(); + * + * const simpleFaker = new SimpleFaker({ randomizer }); + * + * const faker = new Faker({ + * locale: ..., + * randomizer, + * }); + */ +export interface Randomizer { + /** + * Generates a random float between 0 (inclusive) and 1 (exclusive). + * + * @example + * randomizer.next() // 0.3404027920160495 + * randomizer.next() // 0.929890375900335 + * randomizer.next() // 0.5866362918861691 + */ + next(): number; + + /** + * Sets the seed to use. + * + * @param seed The seed to use. + * + * @example + * // Random seeds + * randomizer.seed(Date.now() ^ (Math.random() * 0x100000000)); + * // Fixed seeds (for reproducibility) + * randomizer.seed(42); + * randomizer.seed([42, 13.37]); + */ + seed(seed: number | number[]): void; +} diff --git a/src/simple-faker.ts b/src/simple-faker.ts index c4ecb8df..49fc3f1c 100644 --- a/src/simple-faker.ts +++ b/src/simple-faker.ts @@ -1,10 +1,10 @@ -import type { Mersenne } from './internal/mersenne/mersenne'; -import mersenne from './internal/mersenne/mersenne'; +import { generateMersenne32Randomizer } from './internal/mersenne'; import { DatatypeModule } from './modules/datatype'; import { SimpleDateModule } from './modules/date'; import { SimpleHelpersModule } from './modules/helpers'; import { NumberModule } from './modules/number'; import { StringModule } from './modules/string'; +import type { Randomizer } from './randomizer'; /** * This is a simplified Faker class that doesn't need any localized data to generate its output. @@ -77,7 +77,7 @@ export class SimpleFaker { } /** @internal */ - private readonly _mersenne: Mersenne = mersenne(); + private readonly _randomizer: Randomizer = generateMersenne32Randomizer(); readonly datatype: DatatypeModule = new DatatypeModule(this); readonly date: SimpleDateModule = new SimpleDateModule(this); @@ -89,9 +89,28 @@ export class SimpleFaker { * Creates a new instance of SimpleFaker. * * In nearly any case you should use the prebuilt `simpleFaker` instances instead of the constructor. + * + * @param options The options to use. + * @param options.randomizer The Randomizer to use. + * Specify this only if you want to use it to achieve a specific goal, + * such as sharing the same random generator with other instances/tools. + * Defaults to faker's Mersenne Twister based pseudo random number generator. */ - constructor() { - // This empty constructor just exists for VitePress docs + constructor( + options: { + /** + * The Randomizer to use. + * Specify this only if you want to use it to achieve a specific goal, + * such as sharing the same random generator with other instances/tools. + * + * @default generateMersenne32Randomizer() + */ + randomizer?: Randomizer; + } = {} + ) { + const { randomizer = generateMersenne32Randomizer() } = options; + + this._randomizer = randomizer; } /** @@ -211,7 +230,7 @@ export class SimpleFaker { seed( seed: number | number[] = Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER) ): number | number[] { - this._mersenne.seed(seed); + this._randomizer.seed(seed); return seed; } |
