Skip to content

Commit 5ef8ef1

Browse files
matthewmayerST-DDT
andauthored
feat(number): add multipleOf to faker.number.int (#2586)
Co-authored-by: ST-DDT <ST-DDT@gmx.de>
1 parent 1169a05 commit 5ef8ef1

2 files changed

Lines changed: 106 additions & 11 deletions

File tree

src/modules/number/index.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ export class NumberModule extends SimpleModuleBase {
2323
* @param options Maximum value or options object.
2424
* @param options.min Lower bound for generated number. Defaults to `0`.
2525
* @param options.max Upper bound for generated number. Defaults to `Number.MAX_SAFE_INTEGER`.
26+
* @param options.multipleOf Generated number will be a multiple of the given integer. Defaults to `1`.
2627
*
2728
* @throws When `min` is greater than `max`.
28-
* @throws When there are no integers between `min` and `max`.
29+
* @throws When there are no suitable integers between `min` and `max`.
30+
* @throws When `multipleOf` is not a positive integer.
2931
*
3032
* @see faker.string.numeric(): For generating a `string` of digits with a given length (range).
3133
*
@@ -35,6 +37,7 @@ export class NumberModule extends SimpleModuleBase {
3537
* faker.number.int({ min: 1000000 }) // 2900970162509863
3638
* faker.number.int({ max: 100 }) // 42
3739
* faker.number.int({ min: 10, max: 100 }) // 57
40+
* faker.number.int({ min: 10, max: 100, multipleOf: 10 }) // 50
3841
*
3942
* @since 8.0.0
4043
*/
@@ -54,24 +57,39 @@ export class NumberModule extends SimpleModuleBase {
5457
* @default Number.MAX_SAFE_INTEGER
5558
*/
5659
max?: number;
60+
/**
61+
* Generated number will be a multiple of the given integer.
62+
*
63+
* @default 1
64+
*/
65+
multipleOf?: number;
5766
} = {}
5867
): number {
5968
if (typeof options === 'number') {
6069
options = { max: options };
6170
}
6271

63-
const { min = 0, max = Number.MAX_SAFE_INTEGER } = options;
64-
const effectiveMin = Math.ceil(min);
65-
const effectiveMax = Math.floor(max);
72+
const { min = 0, max = Number.MAX_SAFE_INTEGER, multipleOf = 1 } = options;
73+
74+
if (!Number.isInteger(multipleOf)) {
75+
throw new FakerError(`multipleOf should be an integer.`);
76+
}
77+
78+
if (multipleOf <= 0) {
79+
throw new FakerError(`multipleOf should be greater than 0.`);
80+
}
81+
82+
const effectiveMin = Math.ceil(min / multipleOf);
83+
const effectiveMax = Math.floor(max / multipleOf);
6684

6785
if (effectiveMin === effectiveMax) {
68-
return effectiveMin;
86+
return effectiveMin * multipleOf;
6987
}
7088

7189
if (effectiveMax < effectiveMin) {
7290
if (max >= min) {
7391
throw new FakerError(
74-
`No integer value between ${min} and ${max} found.`
92+
`No suitable integer value between ${min} and ${max} found.`
7593
);
7694
}
7795

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

87106
/**

test/modules/number.spec.ts

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,82 @@ describe('number', () => {
6161
expect(actual).lessThanOrEqual(Number.MAX_SAFE_INTEGER);
6262
});
6363

64+
it('should return an even integer', () => {
65+
const actual = faker.number.int({ multipleOf: 2 });
66+
67+
expect(actual).toBeTypeOf('number');
68+
expect(actual).toSatisfy(Number.isInteger);
69+
expect(actual).toSatisfy((x: number) => x % 2 === 0);
70+
expect(actual).toBeGreaterThanOrEqual(0);
71+
expect(actual).toBeLessThanOrEqual(Number.MAX_SAFE_INTEGER);
72+
});
73+
74+
it('provides numbers with a given multipleOf of 10 with exclusive ends', () => {
75+
const results = [
76+
...new Set(
77+
Array.from({ length: 100 }, () =>
78+
faker.number.int({
79+
min: 12,
80+
max: 37,
81+
multipleOf: 10,
82+
})
83+
)
84+
),
85+
].sort();
86+
expect(results).toEqual([20, 30]);
87+
});
88+
89+
it('provides numbers with a given multipleOf of 10 with inclusive ends', () => {
90+
const results = [
91+
...new Set(
92+
Array.from({ length: 100 }, () =>
93+
faker.number.int({
94+
min: 10,
95+
max: 50,
96+
multipleOf: 10,
97+
})
98+
)
99+
),
100+
].sort();
101+
expect(results).toEqual([10, 20, 30, 40, 50]);
102+
});
103+
104+
it('throws for float multipleOf', () => {
105+
const input = {
106+
min: 0,
107+
max: 10,
108+
multipleOf: 0.1,
109+
};
110+
111+
expect(() => faker.number.int(input)).toThrow(
112+
new FakerError('multipleOf should be an integer.')
113+
);
114+
});
115+
116+
it('throws for negative multipleOf', () => {
117+
const input = {
118+
min: -10,
119+
max: 10,
120+
multipleOf: -1,
121+
};
122+
123+
expect(() => faker.number.int(input)).toThrow(
124+
new FakerError('multipleOf should be greater than 0.')
125+
);
126+
});
127+
128+
it('throws for impossible multipleOf', () => {
129+
const input = {
130+
min: 11,
131+
max: 19,
132+
multipleOf: 10,
133+
};
134+
135+
expect(() => faker.number.int(input)).toThrow(
136+
new FakerError('No suitable integer value between 11 and 19 found.')
137+
);
138+
});
139+
64140
it('should return a random number given a maximum value as Number', () => {
65141
const actual = faker.number.int(10);
66142

@@ -167,7 +243,7 @@ describe('number', () => {
167243
expect(() => {
168244
faker.number.int({ min: 2.1, max: 2.9 });
169245
}).toThrow(
170-
new FakerError(`No integer value between 2.1 and 2.9 found.`)
246+
new FakerError(`No suitable integer value between 2.1 and 2.9 found.`)
171247
);
172248
});
173249
});
@@ -368,7 +444,7 @@ describe('number', () => {
368444
expect(() => {
369445
faker.number.binary({ min: 2.1, max: 2.9 });
370446
}).toThrow(
371-
new FakerError(`No integer value between 2.1 and 2.9 found.`)
447+
new FakerError(`No suitable integer value between 2.1 and 2.9 found.`)
372448
);
373449
});
374450
});
@@ -419,7 +495,7 @@ describe('number', () => {
419495
expect(() => {
420496
faker.number.octal({ min: 2.1, max: 2.9 });
421497
}).toThrow(
422-
new FakerError(`No integer value between 2.1 and 2.9 found.`)
498+
new FakerError(`No suitable integer value between 2.1 and 2.9 found.`)
423499
);
424500
});
425501
});
@@ -467,7 +543,7 @@ describe('number', () => {
467543
expect(() => {
468544
faker.number.hex({ min: 2.1, max: 2.9 });
469545
}).toThrow(
470-
new FakerError(`No integer value between 2.1 and 2.9 found.`)
546+
new FakerError(`No suitable integer value between 2.1 and 2.9 found.`)
471547
);
472548
});
473549
});

0 commit comments

Comments
 (0)