diff --git a/src/datatype.ts b/src/datatype.ts index 7e4a3cc158a..29a4950b9a7 100644 --- a/src/datatype.ts +++ b/src/datatype.ts @@ -1,4 +1,5 @@ import type { Faker } from '.'; +import { FakerError } from './errors/faker-error'; import { deprecated } from './internal/deprecated'; /** @@ -24,7 +25,7 @@ export class Datatype { * @param options.max Upper bound for generated number. Defaults to `min + 99999`. * @param options.precision Precision of the generated number. Defaults to `1`. * - * @throws When options define `max < min` + * @throws When options define `max < min`. * * @example * faker.datatype.number() // 55422 @@ -35,25 +36,25 @@ export class Datatype { * faker.datatype.number({ min: 10, max: 100, precision: 0.01 }) // 36.94 */ number( - options?: number | { min?: number; max?: number; precision?: number } + options: number | { min?: number; max?: number; precision?: number } = 99999 ): number { - const opts = typeof options === 'number' ? { max: options } : options ?? {}; + if (typeof options === 'number') { + options = { max: options }; + } - const min = typeof opts.min === 'number' ? opts.min : 0; - let max = typeof opts.max === 'number' ? opts.max : min + 99999; - const precision = typeof opts.precision === 'number' ? opts.precision : 1; + const { min = 0, precision = 1 } = options; + const max = options.max ?? min + 99999; - if (max < min) { - throw new Error(`Max ${max} should be larger then min ${min}`); + if (max === min) { + return min; } - // Make the range inclusive of the max value - if (max >= 0) { - max += precision; + if (max < min) { + throw new FakerError(`Max ${max} should be larger then min ${min}.`); } const randomNumber = Math.floor( - this.faker.mersenne.rand(max / precision, min / precision) + this.faker.mersenne.rand(max / precision + 1, min / precision) ); // Workaround problem in float point arithmetics for e.g. 6681493 / 0.01 diff --git a/test/datatype.spec.ts b/test/datatype.spec.ts index 8c7d572a9ac..696c501cf32 100644 --- a/test/datatype.spec.ts +++ b/test/datatype.spec.ts @@ -170,7 +170,7 @@ const seededRuns = [ number: new Date('2000-06-14T02:54:42.082Z'), withMin: new Date('2065-11-10T19:27:20.915Z'), withMax: new Date('2001-03-20T11:14:25.251Z'), - withMinMax: new Date('1789-03-26T15:44:45.218Z'), + withMinMax: new Date('1789-03-26T15:44:45.219Z'), }, string: { noArgs: 'wKti5-}$_/', @@ -297,7 +297,7 @@ describe('datatype', () => { expect(() => { faker.datatype.number({ min, max }); - }).toThrowError(`Max ${max} should be larger then min ${min}`); + }).toThrowError(`Max ${max} should be larger then min ${min}.`); }); }); @@ -431,38 +431,73 @@ describe('datatype', () => { describe('number', () => { it('should return a random number given a maximum value as Number', () => { const max = 10; - expect(faker.datatype.number(max)).greaterThanOrEqual(0); - expect(faker.datatype.number(max)).lessThanOrEqual(max); + + const actual = faker.datatype.number(max); + + expect(actual).greaterThanOrEqual(0); + expect(actual).lessThanOrEqual(max); }); it('should return a random number given a maximum value as Object', () => { const options = { max: 10 }; - expect(faker.datatype.number(options)).greaterThanOrEqual(0); - expect(faker.datatype.number(options)).lessThanOrEqual(options.max); + + const actual = faker.datatype.number(options); + + expect(actual).greaterThanOrEqual(0); + expect(actual).lessThanOrEqual(options.max); }); it('should return a random number given a maximum value of 0', () => { const options = { max: 0 }; - expect(faker.datatype.number(options)).toBe(0); + + const actual = faker.datatype.number(options); + + expect(actual).toBe(0); }); it('should return a random number given a negative number minimum and maximum value of 0', () => { const options = { min: -100, max: 0 }; - expect(faker.datatype.number(options)).greaterThanOrEqual( - options.min - ); - expect(faker.datatype.number(options)).lessThanOrEqual(options.max); + + const actual = faker.datatype.number(options); + + expect(actual).greaterThanOrEqual(options.min); + expect(actual).lessThanOrEqual(options.max); }); it('should return a random number between a range', () => { const options = { min: 22, max: 33 }; for (let i = 0; i < 100; i++) { - const randomNumber = faker.datatype.number(options); - expect(randomNumber).greaterThanOrEqual(options.min); - expect(randomNumber).lessThanOrEqual(options.max); + const actual = faker.datatype.number(options); + expect(actual).greaterThanOrEqual(options.min); + expect(actual).lessThanOrEqual(options.max); } }); + it('should return inclusive negative max value', () => { + let foundNegative4 = false; + let foundNegative5 = false; + + for (let iter = 0; iter < 1000; iter++) { + const actual = faker.datatype.number({ min: -5, max: -4 }); + + if (actual === -4) { + foundNegative4 = true; + } else if (actual === -5) { + foundNegative5 = true; + } + + expect(actual).greaterThanOrEqual(-5); + expect(actual).lessThanOrEqual(-4); + + if (foundNegative4 && foundNegative5) { + break; + } + } + + expect(foundNegative4).toBeTruthy(); + expect(foundNegative5).toBeTruthy(); + }); + it('provides numbers with a given precision', () => { const options = { min: 0, max: 1.5, precision: 0.5 }; const results = Array.from( @@ -481,32 +516,27 @@ describe('datatype', () => { it('provides numbers with a with exact precision', () => { const options = { min: 0.5, max: 0.99, precision: 0.01 }; for (let i = 0; i < 100; i++) { - const number = faker.datatype.number(options); - expect(number).toBe(Number(number.toFixed(2))); + const actual = faker.datatype.number(options); + expect(actual).toBe(Number(actual.toFixed(2))); } }); it('should not mutate the input object', () => { - const initalMin = 1; - const initalPrecision = 1; - const initalOtherProperty = 'hello darkness my old friend'; + const initialMin = 1; + const initialPrecision = 1; + const initialOtherProperty = 'hello darkness my old friend'; const input: { min?: number; max?: number; precision?: number; otherProperty: string; - } = { - min: initalMin, - precision: initalPrecision, - otherProperty: initalOtherProperty, - }; - - faker.datatype.number(input); - - expect(input.min).toBe(initalMin); - expect(input.precision).toBe(initalPrecision); - expect(input.max).toBe(undefined); - expect(input.otherProperty).toBe(initalOtherProperty); + } = Object.freeze({ + min: initialMin, + precision: initialPrecision, + otherProperty: initialOtherProperty, + }); + + expect(() => faker.datatype.number(input)).not.toThrow(); }); });