Skip to content

Commit

Permalink
feat(number): add exponentialDistribution function
Browse files Browse the repository at this point in the history
  • Loading branch information
ST-DDT committed Jan 18, 2025
1 parent 192b540 commit 34e62e3
Show file tree
Hide file tree
Showing 5 changed files with 325 additions and 0 deletions.
9 changes: 9 additions & 0 deletions scripts/apidocs/utils/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,22 @@ const htmlSanitizeOptions: sanitizeHtml.IOptions = {
'span',
'strong',
'ul',
'table',
'thead',
'tbody',
'tr',
'th',
'td',
],
allowedAttributes: {
a: ['href', 'target', 'rel'],
button: ['class', 'title'],
div: ['class'],
pre: ['class', 'v-pre', 'tabindex'],
span: ['class', 'style'],
table: ['tabindex'],
th: ['style'],
td: ['style'],
},
selfClosing: [],
};
Expand Down
161 changes: 161 additions & 0 deletions src/modules/number/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -534,4 +534,165 @@ export class NumberModule extends SimpleModuleBase {

return result;
}

/**
* Generates a random number between `min` and `max` using an exponential distribution.
* The lower bound is inclusive, but the upper bound is exclusive.
*
* The following table shows the rough distribution of values generated using `Math.floor(exponentialDistribution({ min: 0, max: 10, base: x }))`:
*
* | Value | Base 0.1 | Base 0.5 | Base 1 | Base 2 | Base 10 |
* | :---: | -------: | -------: | -----: | -----: | ------: |
* | 0 | 4.1% | 7.4% | 10.0% | 13.8% | 27.8% |
* | 1 | 4.5% | 7.8% | 10.0% | 12.5% | 16.9% |
* | 2 | 5.0% | 8.2% | 10.0% | 11.5% | 12.1% |
* | 3 | 5.7% | 8.7% | 10.0% | 10.7% | 9.4% |
* | 4 | 6.6% | 9.3% | 10.0% | 10.0% | 7.8% |
* | 5 | 7.8% | 9.9% | 10.0% | 9.3% | 6.6% |
* | 6 | 9.4% | 10.7% | 10.0% | 8.8% | 5.7% |
* | 7 | 12.1% | 11.5% | 10.0% | 8.2% | 5.0% |
* | 8 | 16.9% | 12.6% | 10.0% | 7.8% | 4.5% |
* | 9 | 27.9% | 13.8% | 10.0% | 7.5% | 4.1% |
*
* The following table shows the rough distribution of values generated using `Math.floor(exponentialDistribution({ min: 0, max: 10, bias: x }))`:
*
* | Value | Bias -9 | Bias -1 | Bias 0 | Bias 1 | Bias 9 |
* | :---: | ------: | ------: | -----: | -----: | -----: |
* | 0 | 27.9% | 13.7% | 10.0% | 7.4% | 4.1% |
* | 1 | 16.9% | 12.5% | 10.0% | 7.8% | 4.5% |
* | 2 | 12.1% | 11.6% | 10.0% | 8.3% | 5.1% |
* | 3 | 9.5% | 10.7% | 10.0% | 8.8% | 5.7% |
* | 4 | 7.8% | 10.0% | 10.0% | 9.3% | 6.6% |
* | 5 | 6.6% | 9.3% | 10.0% | 9.9% | 7.7% |
* | 6 | 5.7% | 8.8% | 10.0% | 10.7% | 9.5% |
* | 7 | 5.0% | 8.2% | 10.0% | 11.5% | 12.1% |
* | 8 | 4.5% | 7.8% | 10.0% | 12.6% | 16.8% |
* | 9 | 4.1% | 7.4% | 10.0% | 13.7% | 27.9% |
*
* @param options The options for generating the number.
* @param options.min The minimum value to generate (inclusive). Defaults to `0`.
* @param options.max The maximum value to generate (exclusive). Defaults to `1`.
* @param options.base The base of the exponential distribution. Should be greater than `0`. Defaults to `2`.
*
* The higher/more above `1` the `base`, the more likely the number will be closer to the minimum value.
* The lower/closer to zero the base, the more likely the number will be closer to the maximum value.
* Values of `1` will generate a uniform distribution.
* The following table shows the rough distribution of values generated using `Math.floor(exponentialDistribution({ min: 0, max: 10, base: x }))`:
*
* | Value | Base 0.1 | Base 0.5 | Base 1 | Base 2 | Base 10 |
* | :---: | -------: | -------: | -----: | -----: | ------: |
* | 0 | 4.1% | 7.4% | 10.0% | 13.8% | 27.8% |
* | 1 | 4.5% | 7.8% | 10.0% | 12.5% | 16.9% |
* | 2 | 5.0% | 8.2% | 10.0% | 11.5% | 12.1% |
* | 3 | 5.7% | 8.7% | 10.0% | 10.7% | 9.4% |
* | 4 | 6.6% | 9.3% | 10.0% | 10.0% | 7.8% |
* | 5 | 7.8% | 9.9% | 10.0% | 9.3% | 6.6% |
* | 6 | 9.4% | 10.7% | 10.0% | 8.8% | 5.7% |
* | 7 | 12.1% | 11.5% | 10.0% | 8.2% | 5.0% |
* | 8 | 16.9% | 12.6% | 10.0% | 7.8% | 4.5% |
* | 9 | 27.9% | 13.8% | 10.0% | 7.5% | 4.1% |
*
* Can alternatively be configured using the `bias` option. `base` takes precedence over `bias`.
* @param options.bias An alternative way to specify the `base`. Also accepts values below zero.
*
* The higher/more positive the `bias`, the more likely the number will be closer to the maximum value.
* The lower/more negative the `bias`, the more likely the number will be closer to the minimum value.
* Values of 0 will generate a uniform distribution.
*
* The following table shows the rough distribution of values generated using `Math.floor(exponentialDistribution({ min: 0, max: 10, bias: x }))`:
*
* | Value | Bias -9 | Bias -1 | Bias 0 | Bias 1 | Bias 9 |
* | :---: | ------: | ------: | -----: | -----: | -----: |
* | 0 | 27.9% | 13.7% | 10.0% | 7.4% | 4.1% |
* | 1 | 16.9% | 12.5% | 10.0% | 7.8% | 4.5% |
* | 2 | 12.1% | 11.6% | 10.0% | 8.3% | 5.1% |
* | 3 | 9.5% | 10.7% | 10.0% | 8.8% | 5.7% |
* | 4 | 7.8% | 10.0% | 10.0% | 9.3% | 6.6% |
* | 5 | 6.6% | 9.3% | 10.0% | 9.9% | 7.7% |
* | 6 | 5.7% | 8.8% | 10.0% | 10.7% | 9.5% |
* | 7 | 5.0% | 8.2% | 10.0% | 11.5% | 12.1% |
* | 8 | 4.5% | 7.8% | 10.0% | 12.6% | 16.8% |
* | 9 | 4.1% | 7.4% | 10.0% | 13.7% | 27.9% |
*
* This option is ignored if `base` is specified.
*
* Defaults to `-1`.
*
* @throws If `base` is less than or equal to `0`.
* @throws If `max` is less than `min`.
*
* @example
* faker.number.exponentialDistribution() // 0.41928964795957224
* faker.number.exponentialDistribution(10) // 1.656598169056771
* faker.number.exponentialDistribution({ min: 10, max: 100 }) // 88.7273250669911
* faker.number.exponentialDistribution({ min: 0, max: 100, base: 10 }) // 6.9442760672808745
* faker.number.exponentialDistribution({ min: 0, max: 100, bias: 10 }) // 67.03715679154617
*
* @since 9.5.0
*/
exponentialDistribution(
options:
| number
| {
/**
* The minimum value to generate (inclusive).
*
* @default 0
*/
min?: number;
/**
* The maximum value to generate (exclusive).
*
* @default 1
*/
max?: number;
/**
* The base of the exponential distribution. Should be greater than 0. Defaults to `2`.
* The higher/more above `1` the `base`, the more likely the number will be closer to the minimum value.
* The lower/closer to zero the `base`, the more likely the number will be closer to the maximum value.
* Values of `1` will generate a uniform distribution.
* Can alternatively be configured using the `bias` option. `base` takes precedence over `bias`.
*
* @default 2
*/
base?: number;
/**
* An alternative way to specify the `base`. Also accepts values below zero.
* The higher/more positive the `bias`, the more likely the number will be closer to the maximum value.
* The lower/more negative the `bias`, the more likely the number will be closer to the minimum value.
* Values of `0` will generate a uniform distribution.
* This option is ignored if `base` is specified.
*
* @default -1
*/
bias?: number;
} = {}
): number {
if (typeof options === 'number') {
options = { min: 0, max: options };
}

const {
min = 0,
max = 1,
bias = -1,
base = bias <= 0 ? -bias + 1 : 1 / (bias + 1),
} = options;

if (base === 1) {
return this.faker.number.float({ min, max });

Check warning on line 683 in src/modules/number/index.ts

View check run for this annotation

Codecov / codecov/patch

src/modules/number/index.ts#L683

Added line #L683 was not covered by tests
} else if (base <= 0) {
throw new FakerError('Base should be greater than 0.');
}

if (max === min) {
return min;

Check warning on line 689 in src/modules/number/index.ts

View check run for this annotation

Codecov / codecov/patch

src/modules/number/index.ts#L689

Added line #L689 was not covered by tests
} else if (max < min) {
throw new FakerError(`Max ${max} should be greater than min ${min}.`);
}

const exponent = this.faker.number.float();
const factor = (base ** exponent - 1) / (base - 1);
return min + (max - min) * factor;
}
}
42 changes: 42 additions & 0 deletions test/modules/__snapshots__/number.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ exports[`number > 42 > binary > with options 1`] = `"100"`;

exports[`number > 42 > binary > with value 1`] = `"0"`;

exports[`number > 42 > exponentialDistribution > noArgs 1`] = `0.29642623304954707`;

exports[`number > 42 > exponentialDistribution > with high base 1`] = `0.15209599448489752`;

exports[`number > 42 > exponentialDistribution > with high bias 1`] = `0.6420630212280508`;

exports[`number > 42 > exponentialDistribution > with low base 1`] = `0.6420630212280508`;

exports[`number > 42 > exponentialDistribution > with low bias 1`] = `0.15209599448489752`;

exports[`number > 42 > exponentialDistribution > with max 1`] = `2.9642623304954707`;

exports[`number > 42 > exponentialDistribution > with min and max 1`] = `36.67836097445924`;

exports[`number > 42 > float > with max 1`] = `25.84326820046801`;

exports[`number > 42 > float > with min 1`] = `-25.89477488956341`;
Expand Down Expand Up @@ -84,6 +98,20 @@ exports[`number > 1211 > binary > with options 1`] = `"1010"`;

exports[`number > 1211 > binary > with value 1`] = `"1"`;

exports[`number > 1211 > exponentialDistribution > noArgs 1`] = `0.9033226590337899`;

exports[`number > 1211 > exponentialDistribution > with high base 1`] = `0.8313808279511881`;

exports[`number > 1211 > exponentialDistribution > with high bias 1`] = `0.9801213540567579`;

exports[`number > 1211 > exponentialDistribution > with low base 1`] = `0.9801213540567579`;

exports[`number > 1211 > exponentialDistribution > with low bias 1`] = `0.8313808279511881`;

exports[`number > 1211 > exponentialDistribution > with max 1`] = `9.0332265903379`;

exports[`number > 1211 > exponentialDistribution > with min and max 1`] = `91.29903931304109`;

exports[`number > 1211 > float > with max 1`] = `64.06789061927832`;

exports[`number > 1211 > float > with min 1`] = `-2.0736333821888806`;
Expand Down Expand Up @@ -148,6 +176,20 @@ exports[`number > 1337 > binary > with options 1`] = `"10"`;

exports[`number > 1337 > binary > with value 1`] = `"0"`;

exports[`number > 1337 > exponentialDistribution > noArgs 1`] = `0.1991604233570674`;

exports[`number > 1337 > exponentialDistribution > with high base 1`] = `0.092022676113987`;

exports[`number > 1337 > exponentialDistribution > with high bias 1`] = `0.5033501285097729`;

exports[`number > 1337 > exponentialDistribution > with low base 1`] = `0.5033501285097729`;

exports[`number > 1337 > exponentialDistribution > with low bias 1`] = `0.092022676113987`;

exports[`number > 1337 > exponentialDistribution > with max 1`] = `1.991604233570674`;

exports[`number > 1337 > exponentialDistribution > with min and max 1`] = `27.924438102136065`;

exports[`number > 1337 > float > with max 1`] = `18.079702576075135`;

exports[`number > 1337 > float > with min 1`] = `-30.73293897432999`;
Expand Down
112 changes: 112 additions & 0 deletions test/modules/number.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ describe('number', () => {
.it('with max as 3999', { max: 3999 })
.it('with min and max', { min: 100, max: 502 });
});

t.describe('exponentialDistribution', (t) => {
t.it('noArgs')
.it('with max', 10)
.it('with low base', { base: 0.1 })
.it('with high base', { base: 10 })
.it('with low bias', { bias: -9 })
.it('with high bias', { bias: 9 })
.it('with min and max', { min: 10, max: 100 });
});
});

describe(`random seeded tests for seed ${faker.seed()}`, () => {
Expand Down Expand Up @@ -697,6 +707,108 @@ describe('number', () => {
}).toThrow(new FakerError('Max 100 should be greater than min 500.'));
});
});

describe('exponentialDistribution', () => {
it('should generate a number between 0 and 1 by default', () => {
const actual = faker.number.exponentialDistribution();
expect(actual).toBeTypeOf('number');
expect(actual).toBeGreaterThanOrEqual(0);
expect(actual).toBeLessThan(1);
});

it('should generate a number between 0 and 10', () => {
const actual = faker.number.exponentialDistribution(10);
expect(actual).toBeTypeOf('number');
expect(actual).toBeGreaterThanOrEqual(0);
expect(actual).toBeLessThan(10);
});

it('should generate a number between 10 and 100', () => {
const actual = faker.number.exponentialDistribution({
min: 10,
max: 100,
});
expect(actual).toBeTypeOf('number');
expect(actual).toBeGreaterThanOrEqual(10);
expect(actual).toBeLessThan(100);
});

it('should generate a number with low base', () => {
const results = Array.from({ length: 10 }, (_, i) => i);
for (let i = 0; i < 1000; i++) {
results[
Math.floor(
faker.number.exponentialDistribution({ max: 10, base: 0.1 })
)
]++;
}

expect(results[0]).toBeLessThan(75);
expect(results[9]).toBeGreaterThan(200);
});

it('should generate a number with high base', () => {
const results = Array.from({ length: 10 }, (_, i) => i);
for (let i = 0; i < 1000; i++) {
results[
Math.floor(
faker.number.exponentialDistribution({ max: 10, base: 10 })
)
]++;
}

expect(results[0]).toBeGreaterThan(200);
expect(results[9]).toBeLessThan(75);
});

it('should generate a number with low bias', () => {
const results = Array.from({ length: 10 }, (_, i) => i);
for (let i = 0; i < 1000; i++) {
results[
Math.floor(
faker.number.exponentialDistribution({ max: 10, bias: -9 })
)
]++;
}

expect(results[0]).toBeGreaterThan(200);
expect(results[9]).toBeLessThan(75);
});

it('should generate a number with high bias', () => {
const results = Array.from({ length: 10 }, (_, i) => i);
for (let i = 0; i < 1000; i++) {
results[
Math.floor(
faker.number.exponentialDistribution({ max: 10, bias: 9 })
)
]++;
}

expect(results[0]).toBeLessThan(75);
expect(results[9]).toBeGreaterThan(200);
});
});

it('should throw when min > max', () => {
const min = 10;
const max = 9;

expect(() => {
faker.number.exponentialDistribution({ min, max });
}).toThrow(
new FakerError(`Max ${max} should be greater than min ${min}.`)
);
});

it('should throw when base is less than or equal to 0', () => {
expect(() => {
faker.number.exponentialDistribution({ base: 0 });
}).toThrow(new FakerError('Base should be greater than 0.'));
expect(() => {
faker.number.exponentialDistribution({ base: -1 });
}).toThrow(new FakerError('Base should be greater than 0.'));
});
});

describe('value range tests', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ exports[`check docs completeness > all modules and methods are present 1`] = `
[
"bigInt",
"binary",
"exponentialDistribution",
"float",
"hex",
"int",
Expand Down

0 comments on commit 34e62e3

Please sign in to comment.