Skip to content

Commit 9348138

Browse files
ejchengST-DDT
andauthored
fix(commerce): return fractional prices (#2458)
Co-authored-by: ST-DDT <ST-DDT@gmx.de>
1 parent 8e880c1 commit 9348138

4 files changed

Lines changed: 161 additions & 52 deletions

File tree

docs/guide/upgrading_v9/2458.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
### Prices now return more price-like values
2+
3+
The `faker.commerce.price` method now produces values, that also return fractional values.
4+
5+
Old price: 828.00
6+
New price: 828.59
7+
8+
The last digit of the price will adjusted to be more price-like:
9+
10+
- 50% of the time: `9`
11+
- 30% of the time: `5`
12+
- 10% of the time: `0`
13+
- 10% of the time: a random digit from `0` to `9`
14+
15+
We plan to rethink this method some more in the future: [#2579](https://github.com/faker-js/faker/issues/2579)

src/modules/commerce/index.ts

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -116,16 +116,23 @@ export class CommerceModule extends ModuleBase {
116116
/**
117117
* Generates a price between min and max (inclusive).
118118
*
119+
* To better represent real-world prices, when `options.dec` is greater than `0`, the final decimal digit in the returned string will be generated as follows:
120+
*
121+
* - 50% of the time: `9`
122+
* - 30% of the time: `5`
123+
* - 10% of the time: `0`
124+
* - 10% of the time: a random digit from `0` to `9`
125+
*
119126
* @param options An options object.
120127
* @param options.min The minimum price. Defaults to `1`.
121128
* @param options.max The maximum price. Defaults to `1000`.
122129
* @param options.dec The number of decimal places. Defaults to `2`.
123130
* @param options.symbol The currency value to use. Defaults to `''`.
124131
*
125132
* @example
126-
* faker.commerce.price() // 828.00
127-
* faker.commerce.price({ min: 100 }) // 904.00
128-
* faker.commerce.price({ min: 100, max: 200 }) // 154.00
133+
* faker.commerce.price() // 828.07
134+
* faker.commerce.price({ min: 100 }) // 904.19
135+
* faker.commerce.price({ min: 100, max: 200 }) // 154.55
129136
* faker.commerce.price({ min: 100, max: 200, dec: 0 }) // 133
130137
* faker.commerce.price({ min: 100, max: 200, dec: 0, symbol: '$' }) // $114
131138
*
@@ -160,15 +167,22 @@ export class CommerceModule extends ModuleBase {
160167
/**
161168
* Generates a price between min and max (inclusive).
162169
*
170+
* To better represent real-world prices, when `options.dec` is greater than `0`, the final decimal digit in the returned string will be generated as follows:
171+
*
172+
* - 50% of the time: `9`
173+
* - 30% of the time: `5`
174+
* - 10% of the time: `0`
175+
* - 10% of the time: a random digit from `0` to `9`
176+
*
163177
* @param min The minimum price. Defaults to `1`.
164178
* @param max The maximum price. Defaults to `1000`.
165179
* @param dec The number of decimal places. Defaults to `2`.
166180
* @param symbol The currency value to use. Defaults to `''`.
167181
*
168182
* @example
169-
* faker.commerce.price() // 828.00
170-
* faker.commerce.price(100) // 904.00
171-
* faker.commerce.price(100, 200) // 154.00
183+
* faker.commerce.price() // 828.07
184+
* faker.commerce.price(100) // 904.19
185+
* faker.commerce.price(100, 200) // 154.55
172186
* faker.commerce.price(100, 200, 0) // 133
173187
* faker.commerce.price(100, 200, 0, '$') // $114
174188
*
@@ -180,7 +194,14 @@ export class CommerceModule extends ModuleBase {
180194
/**
181195
* Generates a price between min and max (inclusive).
182196
*
183-
* @param options The minimum price or on options object.
197+
* To better represent real-world prices, when `options.dec` is greater than `0`, the final decimal digit in the returned string will be generated as follows:
198+
*
199+
* - 50% of the time: `9`
200+
* - 30% of the time: `5`
201+
* - 10% of the time: `0`
202+
* - 10% of the time: a random digit from `0` to `9`
203+
*
204+
* @param options The minimum price or an options object.
184205
* @param options.min The minimum price. Defaults to `1`.
185206
* @param options.max The maximum price. Defaults to `1000`.
186207
* @param options.dec The number of decimal places. Defaults to `2`.
@@ -190,9 +211,9 @@ export class CommerceModule extends ModuleBase {
190211
* @param legacySymbol The currency value to use. This argument is deprecated. Defaults to `''`.
191212
*
192213
* @example
193-
* faker.commerce.price() // 828.00
194-
* faker.commerce.price({ min: 100 }) // 904.00
195-
* faker.commerce.price({ min: 100, max: 200 }) // 154.00
214+
* faker.commerce.price() // 828.07
215+
* faker.commerce.price({ min: 100 }) // 904.19
216+
* faker.commerce.price({ min: 100, max: 200 }) // 154.55
196217
* faker.commerce.price({ min: 100, max: 200, dec: 0 }) // 133
197218
* faker.commerce.price({ min: 100, max: 200, dec: 0, symbol: '$' }) // $114
198219
*
@@ -234,7 +255,14 @@ export class CommerceModule extends ModuleBase {
234255
/**
235256
* Generates a price between min and max (inclusive).
236257
*
237-
* @param options The minimum price or on options object.
258+
* To better represent real-world prices, when `options.dec` is greater than `0`, the final decimal digit in the returned string will be generated as follows:
259+
*
260+
* - 50% of the time: `9`
261+
* - 30% of the time: `5`
262+
* - 10% of the time: `0`
263+
* - 10% of the time: a random digit from `0` to `9`
264+
*
265+
* @param options The minimum price or an options object.
238266
* @param options.min The minimum price. Defaults to `1`.
239267
* @param options.max The maximum price. Defaults to `1000`.
240268
* @param options.dec The number of decimal places. Defaults to `2`.
@@ -244,9 +272,9 @@ export class CommerceModule extends ModuleBase {
244272
* @param legacySymbol The currency value to use. This argument is deprecated. Defaults to `''`.
245273
*
246274
* @example
247-
* faker.commerce.price() // 828.00
248-
* faker.commerce.price({ min: 100 }) // 904.00
249-
* faker.commerce.price({ min: 100, max: 200 }) // 154.00
275+
* faker.commerce.price() // 828.07
276+
* faker.commerce.price({ min: 100 }) // 904.19
277+
* faker.commerce.price({ min: 100, max: 200 }) // 154.55
250278
* faker.commerce.price({ min: 100, max: 200, dec: 0 }) // 133
251279
* faker.commerce.price({ min: 100, max: 200, dec: 0, symbol: '$' }) // $114
252280
*
@@ -286,10 +314,41 @@ export class CommerceModule extends ModuleBase {
286314
return `${symbol}0`;
287315
}
288316

289-
// TODO @Shinigami92 2022-11-24: https://github.com/faker-js/faker/issues/350
290-
const randValue = this.faker.number.int({ min, max });
317+
if (min === max) {
318+
return `${symbol}${min.toFixed(dec)}`;
319+
}
320+
321+
const generated = this.faker.number.float({
322+
min,
323+
max,
324+
fractionDigits: dec,
325+
});
326+
327+
if (dec === 0) {
328+
return `${symbol}${generated.toFixed(dec)}`;
329+
}
330+
331+
const oldLastDigit = (generated * 10 ** dec) % 10;
332+
const newLastDigit = this.faker.helpers.weightedArrayElement([
333+
{ weight: 5, value: 9 },
334+
{ weight: 3, value: 5 },
335+
{ weight: 1, value: 0 },
336+
{
337+
weight: 1,
338+
value: this.faker.number.int({ min: 0, max: 9 }),
339+
},
340+
]);
341+
342+
const fraction = (1 / 10) ** dec;
343+
const oldLastDigitValue = oldLastDigit * fraction;
344+
const newLastDigitValue = newLastDigit * fraction;
345+
const combined = generated - oldLastDigitValue + newLastDigitValue;
346+
347+
if (min <= combined && combined <= max) {
348+
return `${symbol}${combined.toFixed(dec)}`;
349+
}
291350

292-
return symbol + randValue.toFixed(dec);
351+
return `${symbol}${generated.toFixed(dec)}`;
293352
}
294353

295354
/**

test/modules/__snapshots__/commerce.spec.ts.snap

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,29 @@ exports[`commerce > 42 > isbn > with variant 10 and space separators 1`] = `"0 9
1212

1313
exports[`commerce > 42 > isbn > with variant 13 1`] = `"978-0-9751108-6-7"`;
1414

15-
exports[`commerce > 42 > price > noArgs 1`] = `"375.00"`;
15+
exports[`commerce > 42 > price > noArgs 1`] = `"375.15"`;
1616

17-
exports[`commerce > 42 > price > with max 1`] = `"375.00"`;
17+
exports[`commerce > 42 > price > with float min and float max option 1`] = `"1.05"`;
1818

19-
exports[`commerce > 42 > price > with max option 1`] = `"501.00"`;
19+
exports[`commerce > 42 > price > with max 1`] = `"375.15"`;
2020

21-
exports[`commerce > 42 > price > with min 1`] = `"406.00"`;
21+
exports[`commerce > 42 > price > with max option 1`] = `"501.35"`;
2222

23-
exports[`commerce > 42 > price > with min and max 1`] = `"69.00"`;
23+
exports[`commerce > 42 > price > with min 1`] = `"405.85"`;
2424

25-
exports[`commerce > 42 > price > with min and max and decimals 1`] = `"69.0000"`;
25+
exports[`commerce > 42 > price > with min and max 1`] = `"68.75"`;
2626

27-
exports[`commerce > 42 > price > with min and max and decimals and symbol 1`] = `"$69.0000"`;
27+
exports[`commerce > 42 > price > with min and max and decimals 1`] = `"68.7275"`;
2828

29-
exports[`commerce > 42 > price > with min and max and decimals and symbol option 1`] = `"$69.0000"`;
29+
exports[`commerce > 42 > price > with min and max and decimals and symbol 1`] = `"$68.7275"`;
3030

31-
exports[`commerce > 42 > price > with min and max and decimals option 1`] = `"69.0000"`;
31+
exports[`commerce > 42 > price > with min and max and decimals and symbol option 1`] = `"$68.7275"`;
3232

33-
exports[`commerce > 42 > price > with min and max option 1`] = `"69.00"`;
33+
exports[`commerce > 42 > price > with min and max and decimals option 1`] = `"68.7275"`;
3434

35-
exports[`commerce > 42 > price > with min option 1`] = `"401.00"`;
35+
exports[`commerce > 42 > price > with min and max option 1`] = `"68.75"`;
36+
37+
exports[`commerce > 42 > price > with min option 1`] = `"400.85"`;
3638

3739
exports[`commerce > 42 > product 1`] = `"Pants"`;
3840

@@ -56,27 +58,29 @@ exports[`commerce > 1211 > isbn > with variant 10 and space separators 1`] = `"1
5658

5759
exports[`commerce > 1211 > isbn > with variant 13 1`] = `"978-1-82966-736-0"`;
5860

59-
exports[`commerce > 1211 > price > noArgs 1`] = `"929.00"`;
61+
exports[`commerce > 1211 > price > noArgs 1`] = `"928.69"`;
62+
63+
exports[`commerce > 1211 > price > with float min and float max option 1`] = `"1.10"`;
6064

61-
exports[`commerce > 1211 > price > with max 1`] = `"929.00"`;
65+
exports[`commerce > 1211 > price > with max 1`] = `"928.69"`;
6266

63-
exports[`commerce > 1211 > price > with max option 1`] = `"1242.00"`;
67+
exports[`commerce > 1211 > price > with max option 1`] = `"1241.59"`;
6468

65-
exports[`commerce > 1211 > price > with min 1`] = `"933.00"`;
69+
exports[`commerce > 1211 > price > with min 1`] = `"932.19"`;
6670

67-
exports[`commerce > 1211 > price > with min and max 1`] = `"97.00"`;
71+
exports[`commerce > 1211 > price > with min and max 1`] = `"96.49"`;
6872

69-
exports[`commerce > 1211 > price > with min and max and decimals 1`] = `"97.0000"`;
73+
exports[`commerce > 1211 > price > with min and max and decimals 1`] = `"96.4269"`;
7074

71-
exports[`commerce > 1211 > price > with min and max and decimals and symbol 1`] = `"$97.0000"`;
75+
exports[`commerce > 1211 > price > with min and max and decimals and symbol 1`] = `"$96.4269"`;
7276

73-
exports[`commerce > 1211 > price > with min and max and decimals and symbol option 1`] = `"$97.0000"`;
77+
exports[`commerce > 1211 > price > with min and max and decimals and symbol option 1`] = `"$96.4269"`;
7478

75-
exports[`commerce > 1211 > price > with min and max and decimals option 1`] = `"97.0000"`;
79+
exports[`commerce > 1211 > price > with min and max and decimals option 1`] = `"96.4269"`;
7680

77-
exports[`commerce > 1211 > price > with min and max option 1`] = `"97.00"`;
81+
exports[`commerce > 1211 > price > with min and max option 1`] = `"96.49"`;
7882

79-
exports[`commerce > 1211 > price > with min option 1`] = `"932.00"`;
83+
exports[`commerce > 1211 > price > with min option 1`] = `"931.59"`;
8084

8185
exports[`commerce > 1211 > product 1`] = `"Sausages"`;
8286

@@ -100,27 +104,29 @@ exports[`commerce > 1337 > isbn > with variant 10 and space separators 1`] = `"0
100104

101105
exports[`commerce > 1337 > isbn > with variant 13 1`] = `"978-0-12-435297-1"`;
102106

103-
exports[`commerce > 1337 > price > noArgs 1`] = `"263.00"`;
107+
exports[`commerce > 1337 > price > noArgs 1`] = `"262.79"`;
108+
109+
exports[`commerce > 1337 > price > with float min and float max option 1`] = `"1.09"`;
104110

105-
exports[`commerce > 1337 > price > with max 1`] = `"263.00"`;
111+
exports[`commerce > 1337 > price > with max 1`] = `"262.79"`;
106112

107-
exports[`commerce > 1337 > price > with max option 1`] = `"351.00"`;
113+
exports[`commerce > 1337 > price > with max option 1`] = `"351.09"`;
108114

109-
exports[`commerce > 1337 > price > with min 1`] = `"299.00"`;
115+
exports[`commerce > 1337 > price > with min 1`] = `"298.99"`;
110116

111-
exports[`commerce > 1337 > price > with min and max 1`] = `"63.00"`;
117+
exports[`commerce > 1337 > price > with min and max 1`] = `"63.19"`;
112118

113-
exports[`commerce > 1337 > price > with min and max and decimals 1`] = `"63.0000"`;
119+
exports[`commerce > 1337 > price > with min and max and decimals 1`] = `"63.1019"`;
114120

115-
exports[`commerce > 1337 > price > with min and max and decimals and symbol 1`] = `"$63.0000"`;
121+
exports[`commerce > 1337 > price > with min and max and decimals and symbol 1`] = `"$63.1019"`;
116122

117-
exports[`commerce > 1337 > price > with min and max and decimals and symbol option 1`] = `"$63.0000"`;
123+
exports[`commerce > 1337 > price > with min and max and decimals and symbol option 1`] = `"$63.1019"`;
118124

119-
exports[`commerce > 1337 > price > with min and max and decimals option 1`] = `"63.0000"`;
125+
exports[`commerce > 1337 > price > with min and max and decimals option 1`] = `"63.1019"`;
120126

121-
exports[`commerce > 1337 > price > with min and max option 1`] = `"63.00"`;
127+
exports[`commerce > 1337 > price > with min and max option 1`] = `"63.19"`;
122128

123-
exports[`commerce > 1337 > price > with min option 1`] = `"293.00"`;
129+
exports[`commerce > 1337 > price > with min option 1`] = `"293.09"`;
124130

125131
exports[`commerce > 1337 > product 1`] = `"Ball"`;
126132

test/modules/commerce.spec.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ describe('commerce', () => {
2727
.it('with min option', { min: 42 })
2828
.it('with max option', { max: 1337 })
2929
.it('with min and max option', { min: 50, max: 100 })
30+
.it('with float min and float max option', { min: 1, max: 1.1 })
3031
.it('with min and max and decimals option', {
3132
min: 50,
3233
max: 100,
@@ -124,14 +125,42 @@ describe('commerce', () => {
124125
const price = faker.commerce.price(100, 100, 1);
125126

126127
expect(price).toBeTruthy();
127-
expect(price, 'the price should be equal 100.0').toBe('100.0');
128+
expect(price, 'the price should equal 100.0').toBe('100.0');
128129
});
129130

130131
it('should handle argument dec = 0', () => {
131132
const price = faker.commerce.price(100, 100, 0);
132133

133134
expect(price).toBeTruthy();
134-
expect(price, 'the price should be equal 100').toBe('100');
135+
expect(price, 'the price should equal 100').toBe('100');
136+
});
137+
138+
it('should return decimal values between min and max', () => {
139+
const result = faker.helpers.multiple(
140+
() => faker.commerce.price(1, 1.1, 2),
141+
{ count: 50 }
142+
);
143+
144+
for (const price of result) {
145+
const parsedPrice = Number.parseFloat(price);
146+
147+
expect(parsedPrice).toBeLessThanOrEqual(1.1);
148+
expect(parsedPrice).toBeGreaterThanOrEqual(1);
149+
}
150+
});
151+
152+
it('should return values with three decimal places between min and max', () => {
153+
const result = faker.helpers.multiple(
154+
() => faker.commerce.price({ min: 0.001, max: 0.009, dec: 3 }),
155+
{ count: 50 }
156+
);
157+
158+
for (const price of result) {
159+
const parsedPrice = Number.parseFloat(price);
160+
161+
expect(parsedPrice).toBeLessThanOrEqual(0.009);
162+
expect(parsedPrice).toBeGreaterThanOrEqual(0.001);
163+
}
135164
});
136165
});
137166

0 commit comments

Comments
 (0)