From 1ebbead194a9583dbd0f21f136c9d2bf8f84a50f Mon Sep 17 00:00:00 2001 From: Matt Mayer Date: Mon, 30 Jan 2023 20:28:53 +0700 Subject: [PATCH] feat(number)!: default to high precision float (#1675) --- docs/guide/upgrading.md | 19 ++++++++---- src/internal/mersenne/mersenne.ts | 14 ++++----- src/modules/color/index.ts | 8 ++--- src/modules/number/index.ts | 38 +++++++++++++++--------- test/__snapshots__/datatype.spec.ts.snap | 6 ++-- test/__snapshots__/finance.spec.ts.snap | 4 +-- test/__snapshots__/mersenne.spec.ts.snap | 36 ++++------------------ test/__snapshots__/number.spec.ts.snap | 24 +++++++-------- test/mersenne.spec.ts | 26 ++++------------ test/number.spec.ts | 17 +++++++---- 10 files changed, 88 insertions(+), 104 deletions(-) diff --git a/docs/guide/upgrading.md b/docs/guide/upgrading.md index c30ee63ba01..673ab610e82 100644 --- a/docs/guide/upgrading.md +++ b/docs/guide/upgrading.md @@ -103,11 +103,20 @@ The `faker.address.*` methods will continue to work as an alias in v8 and v9, bu The number-related methods previously found in `faker.datatype` have been moved to a new `faker.number` module. For the old `faker.datatype.number` method you should replace with `faker.number.int` or `faker.number.float` depending on the precision required. - faker.datatype.number() //35 - faker.datatype.int() //35 - - faker.datatype.number({precision:0.01}) //35.21 - faker.datatype.float({precision:0.01}) //35.21 +By default, `faker.number.float` no longer defaults to a precision of 0.01 + +```js +// OLD +faker.datatype.number({ max: 100 }); // 35 +faker.datatype.number({ max: 100, precision: 0.01 }); // 35.21 +faker.datatype.float({ max: 100 }); // 35.21 +faker.datatype.float({ max: 100, precision: 0.001 }); // 35.211 + +// NEW +faker.number.int({ max: 100 }); // 35 +faker.number.float({ max: 100 }); // 35.21092065742612 +faker.number.float({ max: 100, precision: 0.01 }); // 35.21 +``` | Old method | New method | | ----------------------- | ------------------------------------------ | diff --git a/src/internal/mersenne/mersenne.ts b/src/internal/mersenne/mersenne.ts index e7c0355ce4f..c823af7e2d2 100644 --- a/src/internal/mersenne/mersenne.ts +++ b/src/internal/mersenne/mersenne.ts @@ -7,13 +7,10 @@ import Twister from './twister'; */ export interface Mersenne { /** - * Generates a random number between `[min, max)`. The result is already floored. - * - * @param options The options to generate a random number. - * @param options.min The minimum number. - * @param options.max The maximum number. + * 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(options: { max: number; min: number }): number; + next(): number; /** * Sets the seed to use. @@ -34,10 +31,9 @@ export default function mersenne(): Mersenne { twister.initGenrand(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)); return { - next({ min, max }): number { - return Math.floor(twister.genrandReal2() * (max - min) + min); + next(): number { + return twister.genrandReal2(); }, - seed(seed: number | number[]): void { if (typeof seed === 'number') { twister.initGenrand(seed); diff --git a/src/modules/color/index.ts b/src/modules/color/index.ts index 9831d2589c1..981dae60555 100644 --- a/src/modules/color/index.ts +++ b/src/modules/color/index.ts @@ -377,7 +377,7 @@ export class ColorModule { color = Array.from({ length: 3 }, () => this.faker.number.int(255)); if (includeAlpha) { - color.push(this.faker.number.float()); + color.push(this.faker.number.float({ precision: 0.01 })); cssFunction = 'rgba'; } @@ -458,7 +458,7 @@ export class ColorModule { }): string | number[]; cmyk(options?: { format?: ColorFormat }): string | number[] { const color: string | number[] = Array.from({ length: 4 }, () => - this.faker.number.float() + this.faker.number.float({ precision: 0.01 }) ); return toColorFormat(color, options?.format || 'decimal', 'cmyk'); } @@ -568,7 +568,7 @@ export class ColorModule { }): string | number[] { const hsl: number[] = [this.faker.number.int(360)]; for (let i = 0; i < (options?.includeAlpha ? 3 : 2); i++) { - hsl.push(this.faker.number.float()); + hsl.push(this.faker.number.float({ precision: 0.01 })); } return toColorFormat( @@ -674,7 +674,7 @@ export class ColorModule { }): string | number[] { const hsl: number[] = [this.faker.number.int(360)]; for (let i = 0; i < 2; i++) { - hsl.push(this.faker.number.float()); + hsl.push(this.faker.number.float({ precision: 0.01 })); } return toColorFormat(hsl, options?.format || 'decimal', 'hwb'); diff --git a/src/modules/number/index.ts b/src/modules/number/index.ts index 13368990d14..96e5067d0c4 100644 --- a/src/modules/number/index.ts +++ b/src/modules/number/index.ts @@ -81,8 +81,8 @@ export class NumberModule { const mersenne: Mersenne = // @ts-expect-error: access private member field this.faker._mersenne; - - return mersenne.next({ min: effectiveMin, max: effectiveMax + 1 }); + const real = mersenne.next(); + return Math.floor(real * (effectiveMax + 1 - effectiveMin) + effectiveMin); } /** @@ -91,13 +91,13 @@ export class NumberModule { * @param options Upper bound or options object. Defaults to `{}`. * @param options.min Lower bound for generated number. Defaults to `0.0`. * @param options.max Upper bound for generated number. Defaults to `1.0`. - * @param options.precision Precision of the generated number. Defaults to `0.01`. + * @param options.precision Precision of the generated number, for example `0.01` will round to 2 decimal points. * * @example - * faker.number.float() // 0.89 - * faker.number.float(3) // 1.14 - * faker.number.float({ min: -1000000 }) // -823469.91 - * faker.number.float({ max: 100 }) // 27.28 + * faker.number.float() // 0.5688541042618454 + * faker.number.float(3) // 2.367973240558058 + * faker.number.float({ min: -1000000 }) //-780678.849672846 + * faker.number.float({ max: 100 }) // 17.3687307164073 * faker.number.float({ precision: 0.1 }) // 0.9 * faker.number.float({ min: 10, max: 100, precision: 0.001 }) // 35.415 * @@ -133,7 +133,7 @@ export class NumberModule { }; } - const { min = 0, max = 1, precision = 0.01 } = options; + const { min = 0, max = 1, precision } = options; if (max === min) { return min; @@ -143,13 +143,23 @@ export class NumberModule { throw new FakerError(`Max ${max} should be greater than min ${min}.`); } - const factor = 1 / precision; - const int = this.int({ - min: min * factor, - max: max * factor, - }); + if (precision !== undefined) { + if (precision <= 0) { + throw new FakerError(`Precision should be greater than 0.`); + } - return int / factor; + const factor = 1 / precision; + const int = this.int({ + min: min * factor, + max: max * factor, + }); + return int / factor; + } else { + // @ts-expect-error: access private member field + const mersenne: Mersenne = this.faker._mersenne; + const real = mersenne.next(); + return real * (max - min) + min; + } } /** diff --git a/test/__snapshots__/datatype.spec.ts.snap b/test/__snapshots__/datatype.spec.ts.snap index b4dae898336..eece8554ea6 100644 --- a/test/__snapshots__/datatype.spec.ts.snap +++ b/test/__snapshots__/datatype.spec.ts.snap @@ -267,8 +267,8 @@ exports[`datatype > 1337 > array > noArgs 1`] = ` "e|/Jqjjj!B", "GDWQgC2M;q", 3648103756333056, - 3967686428065792, - ".Gm3tRwnZ2", + "I1.Gm3tRwn", + 1668592164667392, 8623125245722624, 3831794621218816, ] @@ -351,7 +351,7 @@ exports[`datatype > 1337 > hexadecimal > with length, prefix, and casing 1`] = ` exports[`datatype > 1337 > hexadecimal > with prefix 1`] = `"0x5"`; -exports[`datatype > 1337 > json 1`] = `"{\\"foo\\":\\"U/4:SK$>6Q\\",\\"bar\\":2359372120326144,\\"bike\\":\\"{:e=+kD)[B\\",\\"a\\":\\"e|/Jqjjj!B\\",\\"b\\":\\"GDWQgC2M;q\\",\\"name\\":3648103756333056,\\"prop\\":3967686428065792}"`; +exports[`datatype > 1337 > json 1`] = `"{\\"foo\\":\\"U/4:SK$>6Q\\",\\"bar\\":2359372120326144,\\"bike\\":\\"{:e=+kD)[B\\",\\"a\\":\\"e|/Jqjjj!B\\",\\"b\\":\\"GDWQgC2M;q\\",\\"name\\":3648103756333056,\\"prop\\":\\"I1.Gm3tRwn\\"}"`; exports[`datatype > 1337 > number > noArgs 1`] = `26202`; diff --git a/test/__snapshots__/finance.spec.ts.snap b/test/__snapshots__/finance.spec.ts.snap index 95fa5c2618a..937b84efc9b 100644 --- a/test/__snapshots__/finance.spec.ts.snap +++ b/test/__snapshots__/finance.spec.ts.snap @@ -38,9 +38,9 @@ exports[`finance > 42 > currencySymbol 1`] = `"₱"`; exports[`finance > 42 > ethereumAddress 1`] = `"0x8be4abdd39321ad7d3fe01ffce404f4d6db0906b"`; -exports[`finance > 42 > iban > noArgs 1`] = `"GT30Y75110867098F1E3542612J4"`; +exports[`finance > 42 > iban > noArgs 1`] = `"GT03975110867098F1E3542612J4"`; -exports[`finance > 42 > iban > with formatted 1`] = `"GT30 Y751 1086 7098 F1E3 5426 12J4"`; +exports[`finance > 42 > iban > with formatted 1`] = `"GT03 9751 1086 7098 F1E3 5426 12J4"`; exports[`finance > 42 > iban > with formatted and countryCode 1`] = `"DE47 7175 0020 0086 0600 97"`; diff --git a/test/__snapshots__/mersenne.spec.ts.snap b/test/__snapshots__/mersenne.spec.ts.snap index 21d3e4cc84d..08002528213 100644 --- a/test/__snapshots__/mersenne.spec.ts.snap +++ b/test/__snapshots__/mersenne.spec.ts.snap @@ -1,37 +1,13 @@ // Vitest Snapshot v1 -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 value for next() 1`] = `0.8562037434894592`; -exports[`mersenne twister > seed: [42,1,2] > should return deterministic values for next({ min: -60, max: 0 }) 1`] = `-9`; +exports[`mersenne twister > seed: [1211,1,2] > should return deterministic value for next() 1`] = `0.8916433283593506`; -exports[`mersenne twister > seed: [42,1,2] > should return deterministic values for next({ min: 0, max: 100 }) 1`] = `85`; +exports[`mersenne twister > seed: [1337,1,2] > should return deterministic value for next() 1`] = `0.17990487208589911`; -exports[`mersenne twister > seed: [1211,1,2] > should return deterministic values for next({ min: -50, max: 60 }) 1`] = `48`; +exports[`mersenne twister > seed: 42 > should return deterministic value for next() 1`] = `0.37454011430963874`; -exports[`mersenne twister > seed: [1211,1,2] > should return deterministic values for next({ min: -60, max: 0 }) 1`] = `-7`; +exports[`mersenne twister > seed: 1211 > should return deterministic value for next() 1`] = `0.9285201537422836`; -exports[`mersenne twister > seed: [1211,1,2] > should return deterministic values for next({ min: 0, max: 100 }) 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: [1337,1,2] > should return deterministic values for next({ min: -60, max: 0 }) 1`] = `-50`; - -exports[`mersenne twister > seed: [1337,1,2] > should return deterministic values for next({ min: 0, max: 100 }) 1`] = `17`; - -exports[`mersenne twister > seed: 42 > should return deterministic values for next({ min: -50, max: 60 }) 1`] = `-9`; - -exports[`mersenne twister > seed: 42 > should return deterministic values for next({ min: -60, max: 0 }) 1`] = `-38`; - -exports[`mersenne twister > seed: 42 > should return deterministic values for next({ min: 0, max: 100 }) 1`] = `37`; - -exports[`mersenne twister > seed: 1211 > should return deterministic values for next({ min: -50, max: 60 }) 1`] = `52`; - -exports[`mersenne twister > seed: 1211 > should return deterministic values for next({ min: -60, max: 0 }) 1`] = `-5`; - -exports[`mersenne twister > seed: 1211 > should return deterministic values for next({ min: 0, max: 100 }) 1`] = `92`; - -exports[`mersenne twister > seed: 1337 > should return deterministic values for next({ min: -50, max: 60 }) 1`] = `-22`; - -exports[`mersenne twister > seed: 1337 > should return deterministic values for next({ min: -60, max: 0 }) 1`] = `-45`; - -exports[`mersenne twister > seed: 1337 > should return deterministic values for next({ min: 0, max: 100 }) 1`] = `26`; +exports[`mersenne twister > seed: 1337 > should return deterministic value for next() 1`] = `0.2620246761944145`; diff --git a/test/__snapshots__/number.spec.ts.snap b/test/__snapshots__/number.spec.ts.snap index e699dc9599f..71523ecf8bb 100644 --- a/test/__snapshots__/number.spec.ts.snap +++ b/test/__snapshots__/number.spec.ts.snap @@ -20,15 +20,15 @@ exports[`number > 42 > binary > with options 1`] = `"100"`; exports[`number > 42 > binary > with value 1`] = `"0"`; -exports[`number > 42 > float > with max 1`] = `25.84`; +exports[`number > 42 > float > with max 1`] = `25.843267887365073`; -exports[`number > 42 > float > with min 1`] = `-25.9`; +exports[`number > 42 > float > with min 1`] = `-25.894775084685534`; -exports[`number > 42 > float > with min and max 1`] = `-0.43`; +exports[`number > 42 > float > with min and max 1`] = `-0.4260473116301`; exports[`number > 42 > float > with min, max and precision 1`] = `-0.4261`; -exports[`number > 42 > float > with plain number 1`] = `1.5`; +exports[`number > 42 > float > with plain number 1`] = `1.498160457238555`; exports[`number > 42 > hex > noArgs 1`] = `"5"`; @@ -68,15 +68,15 @@ exports[`number > 1211 > binary > with options 1`] = `"1010"`; exports[`number > 1211 > binary > with value 1`] = `"1"`; -exports[`number > 1211 > float > with max 1`] = `64.07`; +exports[`number > 1211 > float > with max 1`] = `64.06789060821757`; -exports[`number > 1211 > float > with min 1`] = `-2.07`; +exports[`number > 1211 > float > with min 1`] = `-2.073633389081806`; -exports[`number > 1211 > float > with min and max 1`] = `61.07`; +exports[`number > 1211 > float > with min and max 1`] = `61.06573706539348`; exports[`number > 1211 > float > with min, max and precision 1`] = `61.0658`; -exports[`number > 1211 > float > with plain number 1`] = `3.72`; +exports[`number > 1211 > float > with plain number 1`] = `3.7140806149691343`; exports[`number > 1211 > hex > noArgs 1`] = `"e"`; @@ -116,15 +116,15 @@ exports[`number > 1337 > binary > with options 1`] = `"10"`; exports[`number > 1337 > binary > with value 1`] = `"0"`; -exports[`number > 1337 > float > with max 1`] = `18.08`; +exports[`number > 1337 > float > with max 1`] = `18.0797026574146`; -exports[`number > 1337 > float > with min 1`] = `-30.74`; +exports[`number > 1337 > float > with min 1`] = `-30.732938923640177`; -exports[`number > 1337 > float > with min and max 1`] = `-12.92`; +exports[`number > 1337 > float > with min and max 1`] = `-12.915260942419991`; exports[`number > 1337 > float > with min, max and precision 1`] = `-12.9153`; -exports[`number > 1337 > float > with plain number 1`] = `1.05`; +exports[`number > 1337 > float > with plain number 1`] = `1.048098704777658`; exports[`number > 1337 > hex > noArgs 1`] = `"4"`; diff --git a/test/mersenne.spec.ts b/test/mersenne.spec.ts index f6585783063..d495804c33f 100644 --- a/test/mersenne.spec.ts +++ b/test/mersenne.spec.ts @@ -3,12 +3,6 @@ import type { Mersenne } from '../src/internal/mersenne/mersenne'; import mersenneFn from '../src/internal/mersenne/mersenne'; import { seededRuns } from './support/seededRuns'; -const minMaxTestCases = [ - { min: 0, max: 100 }, - { min: -60, max: 0 }, - { min: -50, max: 60 }, -]; - const NON_SEEDED_BASED_RUN = 25; describe('mersenne twister', () => { @@ -24,18 +18,10 @@ describe('mersenne twister', () => { mersenne.seed(seed); }); - for (const { min, max } of minMaxTestCases) { - 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 next({ min: ${0}, max: ${1} })`, () => { - const actual = mersenne.next({ min: 0, max: 1 }); + it(`should return deterministic value for next()`, () => { + const actual = mersenne.next(); - expect(actual).toEqual(0); + expect(actual).toMatchSnapshot(); }); }); } @@ -57,11 +43,11 @@ describe('mersenne twister', () => { for (let i = 1; i <= NON_SEEDED_BASED_RUN; i++) { describe('next', () => { - it('should return random number from interval [min, max)', () => { - const actual = mersenne.next({ min: 0, max: 2 }); + it('should return random number from interval [0, 1)', () => { + const actual = mersenne.next(); expect(actual).toBeGreaterThanOrEqual(0); - expect(actual).toBeLessThan(2); + expect(actual).toBeLessThan(1); }); }); } diff --git a/test/number.spec.ts b/test/number.spec.ts index 91208e00778..78a385b9bac 100644 --- a/test/number.spec.ts +++ b/test/number.spec.ts @@ -142,9 +142,10 @@ describe('number', () => { }); describe('float', () => { - it('should return a random float with a default precision of 2 digits after floating point', () => { + it('should return a random float', () => { const actual = faker.number.float(); - expect(actual).toBe(Number(actual.toFixed(2))); + expect(actual).toBeGreaterThanOrEqual(0); + expect(actual).toBeLessThanOrEqual(1); }); it('should return a random float with given max', () => { @@ -220,10 +221,16 @@ describe('number', () => { } }); - it('provides number with a precision 0', () => { - const actual = faker.number.float({ precision: 0 }); + it('throws an error for precision 0', () => { + expect(() => faker.number.float({ precision: 0 })).toThrowError( + new FakerError('Precision should be greater than 0.') + ); + }); - expect(actual).toBe(Math.floor(actual)); + it('throws an error for negative precision', () => { + expect(() => faker.number.float({ precision: -0.01 })).toThrowError( + new FakerError('Precision should be greater than 0.') + ); }); it('should not modify the input object', () => {