Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To not couple the number tests with the implementation details of our given distributor functions, we should probably use custom, "deterministic" ones in theses test cases. Additionally, we should add separate test cases for the provided distributor functions. For example: The exponential distributor is currently not being checked for a negative "base" input.

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
Loading