Skip to content

Commit

Permalink
feat(number)!: default to high precision float (#1675)
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Mayer committed Jan 30, 2023
1 parent 0663048 commit 1ebbead
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 104 deletions.
19 changes: 14 additions & 5 deletions docs/guide/upgrading.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| ----------------------- | ------------------------------------------ |
Expand Down
14 changes: 5 additions & 9 deletions src/internal/mersenne/mersenne.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions src/modules/color/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}

Expand Down Expand Up @@ -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');
}
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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');
Expand Down
38 changes: 24 additions & 14 deletions src/modules/number/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand All @@ -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
*
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
}

/**
Expand Down
6 changes: 3 additions & 3 deletions test/__snapshots__/datatype.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,8 @@ exports[`datatype > 1337 > array > noArgs 1`] = `
"e|/Jqjjj!B",
"GDWQgC2M;q",
3648103756333056,
3967686428065792,
".Gm3tRwnZ2",
"I1.Gm3tRwn",
1668592164667392,
8623125245722624,
3831794621218816,
]
Expand Down Expand Up @@ -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`;
Expand Down
4 changes: 2 additions & 2 deletions test/__snapshots__/finance.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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"`;

Expand Down
36 changes: 6 additions & 30 deletions test/__snapshots__/mersenne.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -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`;
24 changes: 12 additions & 12 deletions test/__snapshots__/number.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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"`;

Expand Down Expand Up @@ -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"`;

Expand Down Expand Up @@ -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"`;

Expand Down
26 changes: 6 additions & 20 deletions test/mersenne.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand All @@ -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();
});
});
}
Expand All @@ -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);
});
});
}
Expand Down
17 changes: 12 additions & 5 deletions test/number.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down

0 comments on commit 1ebbead

Please sign in to comment.