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
33 changes: 26 additions & 7 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 will be a multiple of the given integer. Defaults to `1`.
*
* @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,24 +57,39 @@ export class NumberModule extends SimpleModuleBase {
* @default Number.MAX_SAFE_INTEGER
*/
max?: number;
/**
* Generated number will 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 effectiveMin = Math.ceil(min);
const effectiveMax = Math.floor(max);
const { min = 0, max = Number.MAX_SAFE_INTEGER, multipleOf = 1 } = options;

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

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

const effectiveMin = Math.ceil(min / multipleOf);
const effectiveMax = Math.floor(max / multipleOf);

if (effectiveMin === effectiveMax) {
return effectiveMin;
return effectiveMin * multipleOf;
}

if (effectiveMax < effectiveMin) {
if (max >= min) {
throw new FakerError(
`No integer value between ${min} and ${max} found.`
`No suitable integer value between ${min} and ${max} found.`
);
}

Expand All @@ -81,7 +99,8 @@ export class NumberModule extends SimpleModuleBase {
// @ts-expect-error: access private member field
const randomizer = this.faker._randomizer;
const real = randomizer.next();
return Math.floor(real * (effectiveMax + 1 - effectiveMin) + effectiveMin);
const delta = effectiveMax - effectiveMin + 1; // +1 for inclusive max bounds and even distribution
return Math.floor(real * delta + effectiveMin) * multipleOf;
}

/**
Expand Down
84 changes: 80 additions & 4 deletions test/modules/number.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,82 @@ 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', () => {
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', () => {
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', () => {
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', () => {
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 Expand Up @@ -167,7 +243,7 @@ describe('number', () => {
expect(() => {
faker.number.int({ min: 2.1, max: 2.9 });
}).toThrow(
new FakerError(`No integer value between 2.1 and 2.9 found.`)
new FakerError(`No suitable integer value between 2.1 and 2.9 found.`)
);
});
});
Expand Down Expand Up @@ -368,7 +444,7 @@ describe('number', () => {
expect(() => {
faker.number.binary({ min: 2.1, max: 2.9 });
}).toThrow(
new FakerError(`No integer value between 2.1 and 2.9 found.`)
new FakerError(`No suitable integer value between 2.1 and 2.9 found.`)
);
});
});
Expand Down Expand Up @@ -419,7 +495,7 @@ describe('number', () => {
expect(() => {
faker.number.octal({ min: 2.1, max: 2.9 });
}).toThrow(
new FakerError(`No integer value between 2.1 and 2.9 found.`)
new FakerError(`No suitable integer value between 2.1 and 2.9 found.`)
);
});
});
Expand Down Expand Up @@ -467,7 +543,7 @@ describe('number', () => {
expect(() => {
faker.number.hex({ min: 2.1, max: 2.9 });
}).toThrow(
new FakerError(`No integer value between 2.1 and 2.9 found.`)
new FakerError(`No suitable integer value between 2.1 and 2.9 found.`)
);
});
});
Expand Down