Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(number): add multipleOf to faker.number.int #2586

Merged
merged 13 commits into from
Mar 13, 2024
34 changes: 32 additions & 2 deletions src/modules/number/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ export class NumberModule extends SimpleModuleBase {
* @param options Maximum value or options object.
* @param options.min Lower bound for generated number. Defaults to `0`.
* @param options.max Upper bound for generated number. Defaults to `Number.MAX_SAFE_INTEGER`.
* @param options.multipleOf Generated number must be a multiple of the given integer. Defaults to `1`.
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
*
* @throws When `min` is greater than `max`.
* @throws When there are no integers between `min` and `max`.
* @throws When there are no suitable integers between `min` and `max`.
* @throws When `multipleOf` is not a positive integer.
*
* @see faker.string.numeric(): For generating a `string` of digits with a given length (range).
*
Expand All @@ -35,6 +37,7 @@ export class NumberModule extends SimpleModuleBase {
* faker.number.int({ min: 1000000 }) // 2900970162509863
* faker.number.int({ max: 100 }) // 42
* faker.number.int({ min: 10, max: 100 }) // 57
* faker.number.int({ min: 10, max: 100, multipleOf: 10 }) // 50
*
* @since 8.0.0
*/
Expand All @@ -54,13 +57,19 @@ export class NumberModule extends SimpleModuleBase {
* @default Number.MAX_SAFE_INTEGER
*/
max?: number;
/**
* Generated number must be a multiple of the given integer.
*
* @default 1
*/
multipleOf?: number;
} = {}
): number {
if (typeof options === 'number') {
options = { max: options };
}

const { min = 0, max = Number.MAX_SAFE_INTEGER } = options;
const { min = 0, max = Number.MAX_SAFE_INTEGER, multipleOf = 1 } = options;
const effectiveMin = Math.ceil(min);
const effectiveMax = Math.floor(max);

Expand All @@ -78,9 +87,30 @@ export class NumberModule extends SimpleModuleBase {
throw new FakerError(`Max ${max} should be greater than min ${min}.`);
}

if (!Number.isInteger(multipleOf)) {
throw new FakerError(`multipleOf should be an integer.`);
}

if (multipleOf <= 0) {
throw new FakerError(`multipleOf should be greater than 0.`);
}

// @ts-expect-error: access private member field
const randomizer = this.faker._randomizer;
const real = randomizer.next();
if (multipleOf > 1) {
const minMultiple = Math.ceil(effectiveMin / multipleOf) * multipleOf;
const maxMultiple = Math.floor(effectiveMax / multipleOf) * multipleOf;
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
if (maxMultiple < minMultiple) {
throw new FakerError(
`No suitable integer value between ${min} and ${max} found.`
);
}

const delta = (maxMultiple - minMultiple) / multipleOf + 1;
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
return Math.floor(real * delta) * multipleOf + minMultiple;
}

return Math.floor(real * (effectiveMax + 1 - effectiveMin) + effectiveMin);
}

Expand Down
72 changes: 72 additions & 0 deletions test/modules/number.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,78 @@ describe('number', () => {
expect(actual).lessThanOrEqual(Number.MAX_SAFE_INTEGER);
});

it('should return an even integer', () => {
const actual = faker.number.int({ multipleOf: 2 });

expect(actual).toBeTypeOf('number');
expect(actual).toSatisfy(Number.isInteger);
expect(actual).toSatisfy((x: number) => x % 2 === 0);
expect(actual).toBeGreaterThanOrEqual(0);
expect(actual).toBeLessThanOrEqual(Number.MAX_SAFE_INTEGER);
});

it('provides numbers with a given multipleOf of 10 with exclusive ends', () => {
const results = [
...new Set(
Array.from({ length: 100 }, () =>
faker.number.int({
min: 12,
max: 37,
multipleOf: 10,
})
)
),
].sort();
expect(results).toEqual([20, 30]);
});
it('provides numbers with a given multipleOf of 10 with inclusive ends', () => {
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
const results = [
...new Set(
Array.from({ length: 100 }, () =>
faker.number.int({
min: 10,
max: 50,
multipleOf: 10,
})
)
),
].sort();
expect(results).toEqual([10, 20, 30, 40, 50]);
});
it('throws for float multipleOf', () => {
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
const input = {
min: 0,
max: 10,
multipleOf: 0.1,
};

expect(() => faker.number.int(input)).toThrow(
new FakerError('multipleOf should be an integer.')
);
});
it('throws for negative multipleOf', () => {
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
const input = {
min: -10,
max: 10,
multipleOf: -1,
};

expect(() => faker.number.int(input)).toThrow(
new FakerError('multipleOf should be greater than 0.')
);
});
it('throws for impossible multipleOf', () => {
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
const input = {
min: 11,
max: 19,
multipleOf: 10,
};

expect(() => faker.number.int(input)).toThrow(
new FakerError('No suitable integer value between 11 and 19 found.')
);
});

it('should return a random number given a maximum value as Number', () => {
const actual = faker.number.int(10);

Expand Down