@ix-xs/node-comfort 1.2.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +340 -0
  2. package/README.md +137 -548
  3. package/index.js +80 -35
  4. package/package.json +161 -33
  5. package/src/Arr.js +1050 -418
  6. package/src/Async.js +817 -0
  7. package/src/Cache.js +433 -0
  8. package/src/Checker.js +1202 -539
  9. package/src/Cli.js +1293 -0
  10. package/src/Color.js +556 -0
  11. package/src/Crypto.js +1082 -0
  12. package/src/Emitter.js +356 -0
  13. package/src/Env.js +689 -0
  14. package/src/FS.js +1626 -894
  15. package/src/Func.js +823 -356
  16. package/src/Http.js +700 -0
  17. package/src/Id.js +568 -200
  18. package/src/Logger.js +963 -378
  19. package/src/Num.js +965 -360
  20. package/src/Obj.js +1077 -379
  21. package/src/SQLite.js +1629 -1005
  22. package/src/Schema.js +2101 -0
  23. package/src/Str.js +1209 -432
  24. package/src/Sys.js +654 -0
  25. package/src/Time.js +1533 -440
  26. package/src/Utils.js +342 -325
  27. package/src/errors.js +335 -0
  28. package/src/internal/shared.js +415 -0
  29. package/types/index.d.ts +735 -526
  30. package/types/src/Arr.d.ts +533 -145
  31. package/types/src/Arr.d.ts.map +1 -1
  32. package/types/src/Async.d.ts +372 -0
  33. package/types/src/Async.d.ts.map +1 -0
  34. package/types/src/Cache.d.ts +221 -0
  35. package/types/src/Cache.d.ts.map +1 -0
  36. package/types/src/Checker.d.ts +651 -232
  37. package/types/src/Checker.d.ts.map +1 -1
  38. package/types/src/Cli.d.ts +664 -0
  39. package/types/src/Cli.d.ts.map +1 -0
  40. package/types/src/Color.d.ts +300 -0
  41. package/types/src/Color.d.ts.map +1 -0
  42. package/types/src/Crypto.d.ts +620 -0
  43. package/types/src/Crypto.d.ts.map +1 -0
  44. package/types/src/Emitter.d.ts +189 -0
  45. package/types/src/Emitter.d.ts.map +1 -0
  46. package/types/src/Env.d.ts +412 -0
  47. package/types/src/Env.d.ts.map +1 -0
  48. package/types/src/FS.d.ts +717 -238
  49. package/types/src/FS.d.ts.map +1 -1
  50. package/types/src/Func.d.ts +508 -135
  51. package/types/src/Func.d.ts.map +1 -1
  52. package/types/src/Http.d.ts +444 -0
  53. package/types/src/Http.d.ts.map +1 -0
  54. package/types/src/Id.d.ts +199 -66
  55. package/types/src/Id.d.ts.map +1 -1
  56. package/types/src/Logger.d.ts +414 -46
  57. package/types/src/Logger.d.ts.map +1 -1
  58. package/types/src/Num.d.ts +514 -137
  59. package/types/src/Num.d.ts.map +1 -1
  60. package/types/src/Obj.d.ts +453 -104
  61. package/types/src/Obj.d.ts.map +1 -1
  62. package/types/src/SQLite.d.ts +754 -308
  63. package/types/src/SQLite.d.ts.map +1 -1
  64. package/types/src/Schema.d.ts +1189 -0
  65. package/types/src/Schema.d.ts.map +1 -0
  66. package/types/src/Str.d.ts +617 -166
  67. package/types/src/Str.d.ts.map +1 -1
  68. package/types/src/Sys.d.ts +314 -0
  69. package/types/src/Sys.d.ts.map +1 -0
  70. package/types/src/Time.d.ts +759 -204
  71. package/types/src/Time.d.ts.map +1 -1
  72. package/types/src/Utils.d.ts +158 -102
  73. package/types/src/Utils.d.ts.map +1 -1
  74. package/types/src/errors.d.ts +316 -0
  75. package/types/src/errors.d.ts.map +1 -0
  76. package/types/src/internal/shared.d.ts +123 -0
  77. package/types/src/internal/shared.d.ts.map +1 -0
  78. package/types/index.d.ts.map +0 -1
package/src/Num.js CHANGED
@@ -1,366 +1,971 @@
1
+ "use strict";
2
+
1
3
  /**
2
- * Number & math utilities.
4
+ * Numbers: clamping, rounding that gets decimals right, statistics, and
5
+ * formatting for humans (sizes, money, ordinals, compact numbers) in any
6
+ * language. Formatting is English by default: change it with `setLocale()`
7
+ * or per call with `{ locale }`.
3
8
  *
4
- * Clamping, rounding, ranges, human-friendly formatting (bytes, durations,
5
- * ordinals, abbreviations) and safe parsing • the stuff `Math` leaves out.
9
+ * @example
10
+ * num.round(1.005, 2); // 1.01
11
+ * num.formatBytes(1536); // "1.5 KB"
12
+ * num.currency(1234.5, "EUR", { locale: "fr" }); // "1 234,50 €"
13
+ * num.percentile(latencies, 95);
6
14
  */
7
- module.exports = {
8
- /**
9
- * Clamps a number between a minimum and maximum.
10
- *
11
- * @example
12
- * nodeComfort.num.clamp(15, 0, 10); // 10
13
- * nodeComfort.num.clamp(-3, 0, 10); // 0
14
- *
15
- * @param {number} value - Value to clamp.
16
- * @param {number} min - Lower bound.
17
- * @param {number} max - Upper bound.
18
- * @returns {number} The clamped value.
19
- */
20
- clamp(value, min, max) {
21
- return Math.min(Math.max(value, min), max);
22
- },
23
-
24
- /**
25
- * Checks whether a number is within an inclusive `[min, max]` range.
26
- *
27
- * @param {number} value - Value to test.
28
- * @param {number} min - Lower bound.
29
- * @param {number} max - Upper bound.
30
- * @returns {boolean} True if within range.
31
- */
32
- inRange(value, min, max) {
33
- return value >= min && value <= max;
34
- },
35
-
36
- /**
37
- * Rounds a number to a given number of decimal places (avoids float artifacts).
38
- *
39
- * @example
40
- * nodeComfort.num.round(1.005, 2); // 1.01
41
- *
42
- * @param {number} value - Value to round.
43
- * @param {number} [decimals=0] - Number of decimal places.
44
- * @returns {number} The rounded number.
45
- */
46
- round(value, decimals = 0) {
47
- const factor = 10 ** decimals;
48
- return Math.round((value + Number.EPSILON) * factor) / factor;
49
- },
50
-
51
- /**
52
- * Rounds a number down to a given number of decimal places.
53
- *
54
- * @param {number} value - Value to round.
55
- * @param {number} [decimals=0] - Number of decimal places.
56
- * @returns {number} The floored number.
57
- */
58
- floor(value, decimals = 0) {
59
- const factor = 10 ** decimals;
60
- return Math.floor(value * factor) / factor;
61
- },
62
-
63
- /**
64
- * Rounds a number up to a given number of decimal places.
65
- *
66
- * @param {number} value - Value to round.
67
- * @param {number} [decimals=0] - Number of decimal places.
68
- * @returns {number} The ceiled number.
69
- */
70
- ceil(value, decimals = 0) {
71
- const factor = 10 ** decimals;
72
- return Math.ceil(value * factor) / factor;
73
- },
74
-
75
- /**
76
- * Linearly interpolates between `start` and `end` by ratio `t` (0..1).
77
- *
78
- * @example
79
- * nodeComfort.num.lerp(0, 100, 0.5); // 50
80
- *
81
- * @param {number} start - Start value.
82
- * @param {number} end - End value.
83
- * @param {number} t - Interpolation factor (usually 0..1).
84
- * @returns {number} The interpolated value.
85
- */
86
- lerp(start, end, t) {
87
- return start + (end - start) * t;
88
- },
89
-
90
- /**
91
- * Re-maps a number from one range to another.
92
- *
93
- * @example
94
- * nodeComfort.num.mapRange(5, 0, 10, 0, 100); // 50
95
- *
96
- * @param {number} value - Value to map.
97
- * @param {number} inMin - Input range minimum.
98
- * @param {number} inMax - Input range maximum.
99
- * @param {number} outMin - Output range minimum.
100
- * @param {number} outMax - Output range maximum.
101
- * @returns {number} The mapped value.
102
- */
103
- mapRange(value, inMin, inMax, outMin, outMax) {
104
- if (inMax === inMin) return outMin;
105
- return ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
106
- },
107
-
108
- /**
109
- * Returns a random number (float) in `[min, max)`.
110
- *
111
- * Not cryptographically secure. For secure randomness use `nodeComfort.id`.
112
- *
113
- * @param {number} [min=0] - Lower bound (inclusive).
114
- * @param {number} [max=1] - Upper bound (exclusive).
115
- * @returns {number} A random float.
116
- */
117
- random(min = 0, max = 1) {
118
- return Math.random() * (max - min) + min;
119
- },
120
-
121
- /**
122
- * Returns a random integer in `[min, max]` (both inclusive).
123
- *
124
- * @example
125
- * nodeComfort.num.randomInt(1, 6); // dice roll
126
- *
127
- * @param {number} min - Lower bound (inclusive).
128
- * @param {number} max - Upper bound (inclusive).
129
- * @returns {number} A random integer.
130
- */
131
- randomInt(min, max) {
132
- min = Math.ceil(min);
133
- max = Math.floor(max);
134
- return Math.floor(Math.random() * (max - min + 1)) + min;
135
- },
136
-
137
- /**
138
- * Sums all numbers in an array.
139
- *
140
- * @param {number[]} values - Array of numbers.
141
- * @returns {number} The sum (0 for an empty array).
142
- */
143
- sum(values) {
144
- return values.reduce((acc, n) => acc + n, 0);
145
- },
146
-
147
- /**
148
- * Returns the arithmetic mean (average) of an array of numbers.
149
- *
150
- * @param {number[]} values - Array of numbers.
151
- * @returns {number} The average (0 for an empty array).
152
- */
153
- average(values) {
154
- return values.length ? this.sum(values) / values.length : 0;
155
- },
156
-
157
- /**
158
- * Returns the median value of an array of numbers.
159
- *
160
- * @param {number[]} values - Array of numbers.
161
- * @returns {number} The median (0 for an empty array).
162
- */
163
- median(values) {
164
- if (!values.length) return 0;
165
- const sorted = [...values].sort((a, b) => a - b);
166
- const mid = Math.floor(sorted.length / 2);
167
- return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
168
- },
169
-
170
- /**
171
- * Returns the smallest number in an array.
172
- *
173
- * @param {number[]} values - Array of numbers.
174
- * @returns {number|undefined} The minimum, or undefined if empty.
175
- */
176
- min(values) {
177
- return values.length ? Math.min(...values) : undefined;
178
- },
179
-
180
- /**
181
- * Returns the largest number in an array.
182
- *
183
- * @param {number[]} values - Array of numbers.
184
- * @returns {number|undefined} The maximum, or undefined if empty.
185
- */
186
- max(values) {
187
- return values.length ? Math.max(...values) : undefined;
188
- },
189
-
190
- /**
191
- * Computes what percentage `value` is of `total`.
192
- *
193
- * @example
194
- * nodeComfort.num.percent(25, 200); // 12.5
195
- *
196
- * @param {number} value - Partial value.
197
- * @param {number} total - Total value.
198
- * @param {number} [decimals=2] - Decimal places to keep.
199
- * @returns {number} The percentage.
200
- */
201
- percent(value, total, decimals = 2) {
202
- if (!total) return 0;
203
- return this.round((value / total) * 100, decimals);
204
- },
205
-
206
- /**
207
- * Formats a byte count into a human-readable string (KB, MB, GB, ...).
208
- *
209
- * By default uses binary sizing (base 1024) with familiar `KB`/`MB` labels.
210
- * Set `binary: false` for SI sizing (base 1000), or `iec: true` for
211
- * strict `KiB`/`MiB` labels.
212
- *
213
- * @example
214
- * nodeComfort.num.formatBytes(1536); // "1.5 KB"
215
- * nodeComfort.num.formatBytes(1073741824); // "1 GB"
216
- * nodeComfort.num.formatBytes(1000, { binary: false }); // "1 KB"
217
- * nodeComfort.num.formatBytes(1024, { iec: true }); // "1 KiB"
218
- *
219
- * @param {number} bytes - Number of bytes.
220
- * @param {{ decimals?: number, binary?: boolean, iec?: boolean }} [options] - Formatting options.
221
- * @returns {string} The human-readable size.
222
- */
223
- formatBytes(bytes, options = {}) {
224
- if (!Number.isFinite(bytes) || bytes === 0) return "0 B";
225
- const decimals = options.decimals ?? 1;
226
- const base = options.binary === false ? 1000 : 1024;
227
- const units = options.iec
228
- ? ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]
229
- : ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
230
- const sign = bytes < 0 ? "-" : "";
231
- bytes = Math.abs(bytes);
232
- const i = Math.min(Math.floor(Math.log(bytes) / Math.log(base)), units.length - 1);
233
- return `${sign}${this.round(bytes / base ** i, decimals)} ${units[i]}`;
234
- },
235
-
236
- /**
237
- * Formats a large number with a compact suffix (K, M, B, T).
238
- *
239
- * @example
240
- * nodeComfort.num.abbreviate(1500); // "1.5K"
241
- * nodeComfort.num.abbreviate(2_400_000); // "2.4M"
242
- *
243
- * @param {number} value - Number to abbreviate.
244
- * @param {number} [decimals=1] - Decimal places.
245
- * @returns {string} The abbreviated number.
246
- */
247
- abbreviate(value, decimals = 1) {
248
- const sign = value < 0 ? "-" : "";
249
- value = Math.abs(value);
250
- const units = [
251
- { v: 1e12, s: "T" },
252
- { v: 1e9, s: "B" },
253
- { v: 1e6, s: "M" },
254
- { v: 1e3, s: "K" },
255
- ];
256
- for (const { v, s } of units) {
257
- if (value >= v) return `${sign}${this.round(value / v, decimals)}${s}`;
258
- }
259
- return `${sign}${value}`;
260
- },
261
-
262
- /**
263
- * Inserts thousands separators into a number.
264
- *
265
- * @example
266
- * nodeComfort.num.thousands(1234567); // "1,234,567"
267
- * nodeComfort.num.thousands(1234.5, " "); // "1 234.5"
268
- *
269
- * @param {number} value - Number to format.
270
- * @param {string} [separator=","] - Thousands separator.
271
- * @returns {string} The formatted number.
272
- */
273
- thousands(value, separator = ",") {
274
- const [int, dec] = String(value).split(".");
275
- const withSep = int.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
276
- return dec ? `${withSep}.${dec}` : withSep;
277
- },
278
-
279
- /**
280
- * Returns the English ordinal suffix for a number ("st", "nd", "rd", "th").
281
- *
282
- * @example
283
- * nodeComfort.num.ordinal(1); // "1st"
284
- * nodeComfort.num.ordinal(22); // "22nd"
285
- *
286
- * @param {number} value - The number.
287
- * @returns {string} The number with its ordinal suffix.
288
- */
289
- ordinal(value) {
290
- const abs = Math.abs(value) % 100;
291
- const suffix =
292
- abs >= 11 && abs <= 13
293
- ? "th"
294
- : ["th", "st", "nd", "rd"][Math.abs(value) % 10] || "th";
295
- return `${value}${suffix}`;
296
- },
297
-
298
- /**
299
- * Parses a value into a number, returning a fallback when it isn't numeric.
300
- *
301
- * @example
302
- * nodeComfort.num.parse("42px"); // 42
303
- * nodeComfort.num.parse("nope", 0); // 0
304
- *
305
- * @param {*} value - Value to parse.
306
- * @param {number} [fallback=NaN] - Value returned when parsing fails.
307
- * @returns {number} The parsed number or the fallback.
308
- */
309
- parse(value, fallback = NaN) {
310
- if (typeof value === "number") return Number.isNaN(value) ? fallback : value;
311
- const parsed = parseFloat(String(value).replace(/[^0-9.eE+-]/g, ""));
312
- return Number.isNaN(parsed) ? fallback : parsed;
313
- },
314
-
315
- /**
316
- * Checks whether a number is even.
317
- * @param {number} value - Number to check.
318
- * @returns {boolean} True if even.
319
- */
320
- isEven(value) {
321
- return value % 2 === 0;
322
- },
323
-
324
- /**
325
- * Checks whether a number is odd.
326
- * @param {number} value - Number to check.
327
- * @returns {boolean} True if odd.
328
- */
329
- isOdd(value) {
330
- return Math.abs(value % 2) === 1;
331
- },
332
-
333
- /**
334
- * Returns an array of numbers from `start` to `end`.
335
- *
336
- * `end` is exclusive. Supports a custom step and descending ranges.
337
- *
338
- * @example
339
- * nodeComfort.num.range(4); // [0, 1, 2, 3]
340
- * nodeComfort.num.range(1, 5); // [1, 2, 3, 4]
341
- * nodeComfort.num.range(0, 10, 2); // [0, 2, 4, 6, 8]
342
- * nodeComfort.num.range(5, 0); // [5, 4, 3, 2, 1]
343
- *
344
- * @param {number} start - Start (or end if it's the only argument).
345
- * @param {number} [end] - End (exclusive).
346
- * @param {number} [step] - Step size (defaults to 1 or -1 based on direction).
347
- * @returns {number[]} The generated range.
348
- */
349
- range(start, end, step) {
350
- if (end === undefined) {
351
- end = start;
352
- start = 0;
353
- }
354
- const descending = end < start;
355
- step = step ?? (descending ? -1 : 1);
356
- if (step === 0) return [];
357
-
358
- const result = [];
359
- if (step > 0) {
360
- for (let i = start; i < end; i += step) result.push(i);
361
- } else {
362
- for (let i = start; i > end; i += step) result.push(i);
15
+
16
+ const { intl, preciseSum } = require("./internal/shared.js");
17
+
18
+ /** The default language, changed with `setLocale()`. */
19
+ const _config = { locale: "en" };
20
+
21
+ /**
22
+ * Options for `formatBytes()`.
23
+ * @typedef {object} FormatBytesOptions
24
+ * @property {number} [decimals] Maximum decimals. Defaults to `1`.
25
+ * @property {boolean} [binary] 1 KB is 1024 bytes when `true`, 1000 when `false`. Defaults to `true`.
26
+ * @property {boolean} [iec] Use `KiB`, `MiB`, `GiB` labels.
27
+ * @property {string} [locale] Formats the number for a language, like `"fr"` for `"1,5 KB"`.
28
+ * @property {string[]} [units] Your own labels from bytes up, like `["o", "Ko", "Mo", "Go", "To"]`.
29
+ */
30
+
31
+ /**
32
+ * Options for `parseBytes()`.
33
+ * @typedef {object} ParseBytesOptions
34
+ * @property {boolean} [binary] 1 KB is 1024 bytes when `true`, 1000 when `false`. `KiB` units are always 1024. Defaults to `true`.
35
+ */
36
+
37
+ /**
38
+ * Options for `format()`.
39
+ * @typedef {object} FormatNumberOptions
40
+ * @property {string} [locale] Language, like `"fr"` or `"en-US"`. Defaults to the one set with `setLocale()`, or `"en"`.
41
+ * @property {number} [decimals] Exact number of decimals.
42
+ * @property {number} [minDecimals] Minimum decimals.
43
+ * @property {number} [maxDecimals] Maximum decimals. Defaults to `3`.
44
+ * @property {boolean} [compact] Short notation like `1.2K` or `3.4M`.
45
+ * @property {"percent"|"decimal"} [style] `"percent"` multiplies by 100 and adds `%`.
46
+ * @property {string} [unit] Any `Intl` unit: `"kilometer"`, `"celsius"`, `"megabyte"`...
47
+ * @property {"short"|"long"|"narrow"} [unitDisplay] How the unit is written. Defaults to `"short"`.
48
+ * @property {"auto"|"always"|"never"|"exceptZero"} [signDisplay] When to show the sign. `"exceptZero"` gives `+5`.
49
+ * @property {boolean} [grouping] Thousands separators. Defaults to `true`.
50
+ */
51
+
52
+ /**
53
+ * Options for `currency()`.
54
+ * @typedef {object} CurrencyOptions
55
+ * @property {string} [locale] Language. Defaults to the one set with `setLocale()`, or `"en"`.
56
+ * @property {number} [decimals] Defaults to the currency's usual decimals (2 for EUR, 0 for JPY).
57
+ * @property {"symbol"|"narrowSymbol"|"code"|"name"} [display] `€`, `€`, `EUR` or `euros`. Defaults to `"symbol"`.
58
+ * @property {boolean} [accounting] Show negative amounts in parentheses, like `($5.00)`.
59
+ */
60
+
61
+ /**
62
+ * Options for `ordinal()`.
63
+ * @typedef {object} OrdinalOptions
64
+ * @property {string} [locale] Built in: `en`, `fr`, `es`, `it`, `pt`, `de`, `nl`. Defaults to the one set with `setLocale()`, or `"en"`.
65
+ * @property {Partial<Record<Intl.LDMLPluralRule, string>>} [suffixes] Your own suffixes, for other languages.
66
+ */
67
+
68
+ /**
69
+ * Options for `variance()` and `stdDev()`.
70
+ * @typedef {object} VarianceOptions
71
+ * @property {boolean} [sample] Sample statistic (divides by n - 1) instead of population.
72
+ */
73
+
74
+ /**
75
+ * Multiplies by 10^exp without floating-point error.
76
+ * @param {number} value
77
+ * @param {number} exp
78
+ * @returns {number}
79
+ */
80
+ const _shift = (value, exp) => {
81
+ const [mantissa, e = "0"] = String(value).split("e");
82
+ return Number(`${mantissa}e${Number(e) + exp}`);
83
+ };
84
+
85
+ /**
86
+ * @param {number} value
87
+ * @param {number} decimals
88
+ * @param {(n: number) => number} fn
89
+ * @returns {number}
90
+ */
91
+ const _roundWith = (value, decimals, fn) => {
92
+ if (!Number.isFinite(value)) return value;
93
+ if (decimals === 0) return fn(value);
94
+ if (decimals > 0 && Number.isInteger(value)) return value;
95
+ return _shift(fn(_shift(value, decimals)), -decimals);
96
+ };
97
+
98
+ /**
99
+ * How many decimals a number is written with, exponent notation included.
100
+ * @param {number} n
101
+ * @returns {number}
102
+ */
103
+ const _decimalsOf = (n) => {
104
+ if (Number.isInteger(n) || !Number.isFinite(n)) return 0;
105
+ const text = String(n);
106
+ const exponent = text.indexOf("e-");
107
+ if (exponent === -1) return text.length - text.indexOf(".") - 1;
108
+ const mantissa = text.slice(0, exponent);
109
+ return Number(text.slice(exponent + 2)) + (mantissa.includes(".") ? mantissa.length - mantissa.indexOf(".") - 1 : 0);
110
+ };
111
+
112
+ /** @param {number} n */
113
+ const _halfAwayFromZero = (n) => Math.sign(n) * Math.round(Math.abs(n)) || 0;
114
+
115
+ /** @type {Array<[number, string]>} */
116
+ const _ABBREVIATIONS = [[1e12, "T"], [1e9, "B"], [1e6, "M"], [1e3, "K"]];
117
+ const _ORDINAL = { type: "ordinal" };
118
+
119
+ const _SIZE_UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
120
+ const _IEC_UNITS = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
121
+
122
+ /** @type {Record<string, Partial<Record<Intl.LDMLPluralRule, string>>>} */
123
+ const _ORDINAL_SUFFIXES = {
124
+ en: { one: "st", two: "nd", few: "rd", other: "th" },
125
+ fr: { one: "er", other: "e" },
126
+ es: { other: "º" },
127
+ it: { other: "º" },
128
+ pt: { other: "º" },
129
+ de: { other: "." },
130
+ nl: { other: "e" },
131
+ };
132
+
133
+ /**
134
+ * Keeps a number between `min` and `max`.
135
+ *
136
+ * @example
137
+ * num.clamp(150, 0, 100); // 100
138
+ * num.clamp(-5, 0, 100); // 0
139
+ *
140
+ * @param {number} value
141
+ * @param {number} min
142
+ * @param {number} max
143
+ * @returns {number}
144
+ */
145
+ function clamp(value, min, max) {
146
+ return Math.min(Math.max(value, min), max);
147
+ }
148
+
149
+ /**
150
+ * Is the number between `min` and `max`, both included?
151
+ *
152
+ * @example
153
+ * num.inRange(10, 0, 10); // true
154
+ *
155
+ * @param {number} value
156
+ * @param {number} min
157
+ * @param {number} max
158
+ * @returns {boolean}
159
+ */
160
+ function inRange(value, min, max) {
161
+ return value >= min && value <= max;
162
+ }
163
+
164
+ /**
165
+ * Wraps a number around a range, like an angle or a carousel index.
166
+ * `max` is excluded.
167
+ *
168
+ * @example
169
+ * num.wrap(370, 0, 360); // 10
170
+ * num.wrap(-1, 0, 5); // 4
171
+ *
172
+ * @param {number} value
173
+ * @param {number} min
174
+ * @param {number} max
175
+ * @returns {number}
176
+ */
177
+ function wrap(value, min, max) {
178
+ const span = max - min;
179
+ if (span <= 0) return min;
180
+ return ((((value - min) % span) + span) % span) + min;
181
+ }
182
+
183
+ /**
184
+ * Rounds to `decimals` places without floating-point surprises:
185
+ * `Math.round(1.005 * 100) / 100` gives `1`, this gives `1.01`. Halves round
186
+ * away from zero, like in a spreadsheet.
187
+ *
188
+ * @example
189
+ * num.round(1.005, 2); // 1.01
190
+ * num.round(1234, -2); // 1200
191
+ * num.round(-2.5); // -3
192
+ *
193
+ * @param {number} value
194
+ * @param {number} [decimals=0] Negative values round to tens, hundreds...
195
+ * @returns {number}
196
+ */
197
+ function round(value, decimals = 0) {
198
+ return _roundWith(value, decimals, _halfAwayFromZero);
199
+ }
200
+
201
+ /**
202
+ * Rounds down to `decimals` places.
203
+ *
204
+ * @example
205
+ * num.floor(1.789, 2); // 1.78
206
+ *
207
+ * @param {number} value
208
+ * @param {number} [decimals=0]
209
+ * @returns {number}
210
+ */
211
+ function floor(value, decimals = 0) {
212
+ return _roundWith(value, decimals, Math.floor);
213
+ }
214
+
215
+ /**
216
+ * Rounds up to `decimals` places.
217
+ *
218
+ * @example
219
+ * num.ceil(1.231, 2); // 1.24
220
+ *
221
+ * @param {number} value
222
+ * @param {number} [decimals=0]
223
+ * @returns {number}
224
+ */
225
+ function ceil(value, decimals = 0) {
226
+ return _roundWith(value, decimals, Math.ceil);
227
+ }
228
+
229
+ /**
230
+ * Rounds to the nearest multiple of `step`.
231
+ *
232
+ * @example
233
+ * num.snap(17, 5); // 15
234
+ * num.snap(0.37, 0.25); // 0.25
235
+ *
236
+ * @param {number} value
237
+ * @param {number} step
238
+ * @returns {number}
239
+ */
240
+ function snap(value, step) {
241
+ if (!step) return value;
242
+ return round(Math.round(value / step) * step, _decimalsOf(step));
243
+ }
244
+
245
+ /**
246
+ * Are two numbers equal within a small tolerance? The right way to compare
247
+ * float results, since `0.1 + 0.2 === 0.3` is `false`.
248
+ *
249
+ * @example
250
+ * num.approxEqual(0.1 + 0.2, 0.3); // true
251
+ *
252
+ * @param {number} a
253
+ * @param {number} b
254
+ * @param {number} [epsilon=Number.EPSILON * 16] Allowed difference, relative to the size of the numbers.
255
+ * @returns {boolean}
256
+ */
257
+ function approxEqual(a, b, epsilon = Number.EPSILON * 16) {
258
+ if (a === b) return true;
259
+ const diff = Math.abs(a - b);
260
+ return diff <= epsilon * Math.max(1, Math.abs(a), Math.abs(b));
261
+ }
262
+
263
+ /**
264
+ * The value at ratio `t` between `start` and `end`.
265
+ *
266
+ * @example
267
+ * num.lerp(0, 100, 0.25); // 25
268
+ *
269
+ * @param {number} start
270
+ * @param {number} end
271
+ * @param {number} t Usually between 0 and 1.
272
+ * @returns {number}
273
+ */
274
+ function lerp(start, end, t) {
275
+ return start + (end - start) * t;
276
+ }
277
+
278
+ /**
279
+ * Moves a number from one range to another, keeping its relative position.
280
+ *
281
+ * @example
282
+ * num.mapRange(5, 0, 10, 0, 100); // 50
283
+ * num.mapRange(75, 0, 100, 1, 0); // 0.25
284
+ *
285
+ * @param {number} value
286
+ * @param {number} inMin
287
+ * @param {number} inMax
288
+ * @param {number} outMin
289
+ * @param {number} outMax
290
+ * @returns {number}
291
+ */
292
+ function mapRange(value, inMin, inMax, outMin, outMax) {
293
+ if (inMax === inMin) return outMin;
294
+ return ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
295
+ }
296
+
297
+ /**
298
+ * A random float from `min` (included) to `max` (excluded). Not for
299
+ * security: it uses `Math.random`.
300
+ *
301
+ * @example
302
+ * num.random(10, 20); // 13.84...
303
+ *
304
+ * @param {number} [min=0]
305
+ * @param {number} [max=1]
306
+ * @returns {number}
307
+ */
308
+ function random(min = 0, max = 1) {
309
+ return Math.random() * (max - min) + min;
310
+ }
311
+
312
+ /**
313
+ * A random integer from `min` to `max`, both included. Not for security.
314
+ *
315
+ * @example
316
+ * num.randomInt(1, 6); // a dice roll
317
+ *
318
+ * @param {number} min
319
+ * @param {number} max
320
+ * @returns {number}
321
+ */
322
+ function randomInt(min, max) {
323
+ const lo = Math.ceil(Math.min(min, max));
324
+ const hi = Math.floor(Math.max(min, max));
325
+ return Math.floor(Math.random() * (hi - lo + 1)) + lo;
326
+ }
327
+
328
+ /**
329
+ * Adds up numbers, without the drift of a naive loop.
330
+ *
331
+ * @example
332
+ * num.sum([1, 2, 3]); // 6
333
+ * num.sum([0.1, 0.2, 0.3]); // 0.6, where a plain loop gives 0.6000000000000001
334
+ *
335
+ * @param {Iterable<number>} values
336
+ * @returns {number}
337
+ */
338
+ function sum(values) {
339
+ return preciseSum(values);
340
+ }
341
+
342
+ /**
343
+ * Multiplies numbers. An empty list gives `1`.
344
+ *
345
+ * @example
346
+ * num.product([2, 3, 4]); // 24
347
+ *
348
+ * @param {Iterable<number>} values
349
+ * @returns {number}
350
+ */
351
+ function product(values) {
352
+ let total = 1;
353
+ for (const n of values) total *= n;
354
+ return total;
355
+ }
356
+
357
+ /**
358
+ * The mean, or `0` for an empty list.
359
+ *
360
+ * @example
361
+ * num.average([2, 4, 9]); // 5
362
+ *
363
+ * @param {Iterable<number>} values
364
+ * @returns {number}
365
+ */
366
+ function average(values) {
367
+ const list = Array.isArray(values) ? values : [...values];
368
+ return list.length ? preciseSum(list) / list.length : 0;
369
+ }
370
+
371
+ /**
372
+ * The middle value once sorted, or `0` for an empty list.
373
+ *
374
+ * @example
375
+ * num.median([3, 1, 2]); // 2
376
+ * num.median([1, 2, 3, 4]); // 2.5
377
+ *
378
+ * @param {Iterable<number>} values
379
+ * @returns {number}
380
+ */
381
+ function median(values) {
382
+ const sorted = [...values].sort((a, b) => a - b);
383
+ if (!sorted.length) return 0;
384
+ const mid = Math.floor(sorted.length / 2);
385
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
386
+ }
387
+
388
+ /**
389
+ * The most frequent values.
390
+ *
391
+ * @example
392
+ * num.mode([1, 1, 2, 2, 3]); // [1, 2]
393
+ *
394
+ * @param {Iterable<number>} values
395
+ * @returns {number[]}
396
+ */
397
+ function mode(values) {
398
+ /** @type {Map<number, number>} */
399
+ const counts = new Map();
400
+ let best = 0;
401
+ for (const n of values) {
402
+ const count = (counts.get(n) ?? 0) + 1;
403
+ counts.set(n, count);
404
+ if (count > best) best = count;
405
+ }
406
+ /** @type {number[]} */
407
+ const out = [];
408
+ for (const [n, count] of counts) if (count === best) out.push(n);
409
+ return out;
410
+ }
411
+
412
+ /**
413
+ * The variance: the average squared distance from the mean.
414
+ *
415
+ * @example
416
+ * num.variance([2, 4, 4, 4, 5, 5, 7, 9]); // 4
417
+ *
418
+ * @param {Iterable<number>} values
419
+ * @param {VarianceOptions} [options]
420
+ * @returns {number}
421
+ */
422
+ function variance(values, options = {}) {
423
+ const list = Array.isArray(values) ? values : [...values];
424
+ const divisor = options.sample ? list.length - 1 : list.length;
425
+ if (divisor <= 0) return 0;
426
+ const mean = preciseSum(list) / list.length;
427
+ let total = 0;
428
+ for (let i = 0; i < list.length; i++) total += (list[i] - mean) ** 2;
429
+ return total / divisor;
430
+ }
431
+
432
+ /**
433
+ * The standard deviation.
434
+ *
435
+ * @example
436
+ * num.stdDev([2, 4, 4, 4, 5, 5, 7, 9]); // 2
437
+ *
438
+ * @param {Iterable<number>} values
439
+ * @param {VarianceOptions} [options]
440
+ * @returns {number}
441
+ */
442
+ function stdDev(values, options = {}) {
443
+ return Math.sqrt(variance(values, options));
444
+ }
445
+
446
+ /**
447
+ * The value under which `p` percent of the values fall, interpolated like
448
+ * Excel's `PERCENTILE.INC`.
449
+ *
450
+ * @example
451
+ * num.percentile([1, 2, 3, 4, 5], 90); // 4.6
452
+ * num.percentile(responseTimes, 95); // p95 latency
453
+ *
454
+ * @param {Iterable<number>} values
455
+ * @param {number} p From 0 to 100.
456
+ * @returns {number} `NaN` for an empty list.
457
+ */
458
+ function percentile(values, p) {
459
+ const sorted = [...values].sort((a, b) => a - b);
460
+ if (!sorted.length) return NaN;
461
+ const rank = (clamp(p, 0, 100) / 100) * (sorted.length - 1);
462
+ const lower = Math.floor(rank);
463
+ const upper = Math.ceil(rank);
464
+ return round(sorted[lower] + (sorted[upper] - sorted[lower]) * (rank - lower), 12);
465
+ }
466
+
467
+ /**
468
+ * The smallest number. Unlike `Math.min(...array)`, huge arrays are fine.
469
+ * Like `Math.min`, a `NaN` in the list gives `NaN`.
470
+ *
471
+ * @param {Iterable<number>} values
472
+ * @returns {number | undefined} `undefined` for an empty list.
473
+ */
474
+ function min(values) {
475
+ let best;
476
+ for (const n of values) {
477
+ if (n !== n) return NaN;
478
+ if (best === undefined || n < best) best = n;
479
+ }
480
+ return best;
481
+ }
482
+
483
+ /**
484
+ * The largest number. Unlike `Math.max(...array)`, huge arrays are fine.
485
+ * Like `Math.max`, a `NaN` in the list gives `NaN`.
486
+ *
487
+ * @param {Iterable<number>} values
488
+ * @returns {number | undefined} `undefined` for an empty list.
489
+ */
490
+ function max(values) {
491
+ let best;
492
+ for (const n of values) {
493
+ if (n !== n) return NaN;
494
+ if (best === undefined || n > best) best = n;
495
+ }
496
+ return best;
497
+ }
498
+
499
+ /**
500
+ * What percentage `value` is of `total`. Returns `0` when `total` is `0`.
501
+ *
502
+ * @example
503
+ * num.percent(25, 200); // 12.5
504
+ * num.percent(1, 3, 1); // 33.3
505
+ *
506
+ * @param {number} value
507
+ * @param {number} total
508
+ * @param {number} [decimals=2]
509
+ * @returns {number}
510
+ */
511
+ function percent(value, total, decimals = 2) {
512
+ if (!total) return 0;
513
+ return round((value / total) * 100, decimals);
514
+ }
515
+
516
+ /**
517
+ * How much a value changed, in percent. Going from `0` to anything else is
518
+ * an infinite change.
519
+ *
520
+ * @example
521
+ * num.percentChange(80, 100); // 25
522
+ * num.percentChange(100, 80); // -20
523
+ *
524
+ * @param {number} from
525
+ * @param {number} to
526
+ * @param {number} [decimals=2]
527
+ * @returns {number}
528
+ */
529
+ function percentChange(from, to, decimals = 2) {
530
+ if (from === to) return 0;
531
+ if (!from) return to > 0 ? Infinity : -Infinity;
532
+ return round(((to - from) / Math.abs(from)) * 100, decimals);
533
+ }
534
+
535
+ /**
536
+ * Greatest common divisor.
537
+ *
538
+ * @example
539
+ * num.gcd(12, 18, 27); // 3
540
+ *
541
+ * @param {...number} values
542
+ * @returns {number}
543
+ */
544
+ function gcd(...values) {
545
+ let result = 0;
546
+ for (const value of values) {
547
+ let x = Math.abs(result);
548
+ let y = Math.abs(value);
549
+ while (y) {
550
+ const rest = x % y;
551
+ x = y;
552
+ y = rest;
363
553
  }
554
+ result = x;
555
+ }
556
+ return result;
557
+ }
558
+
559
+ /**
560
+ * Least common multiple.
561
+ *
562
+ * @example
563
+ * num.lcm(2, 3, 4); // 12
564
+ *
565
+ * @param {...number} values
566
+ * @returns {number}
567
+ */
568
+ function lcm(...values) {
569
+ if (!values.length) return 0;
570
+ // Dividing first keeps the intermediate value small.
571
+ return values.reduce((a, b) => (a === 0 || b === 0 ? 0 : Math.abs((a / gcd(a, b)) * b)));
572
+ }
573
+
574
+ /**
575
+ * Is it a prime number?
576
+ *
577
+ * @example
578
+ * num.isPrime(7); // true
579
+ * num.isPrime(1); // false
580
+ *
581
+ * @param {number} value
582
+ * @returns {boolean}
583
+ */
584
+ function isPrime(value) {
585
+ if (!Number.isInteger(value) || value < 2) return false;
586
+ if (value < 4) return true;
587
+ if (value % 2 === 0 || value % 3 === 0) return false;
588
+ for (let i = 5; i * i <= value; i += 6) {
589
+ if (value % i === 0 || value % (i + 2) === 0) return false;
590
+ }
591
+ return true;
592
+ }
593
+
594
+ /**
595
+ * `n!`. Pass `true` to get an exact bigint, since numbers lose precision
596
+ * past 18!.
597
+ *
598
+ * @example
599
+ * num.factorial(5); // 120
600
+ * num.factorial(25, true); // 15511210043330985984000000n
601
+ *
602
+ * @param {number} n
603
+ * @param {boolean} [asBigInt=false]
604
+ * @returns {number | bigint}
605
+ * @throws {RangeError} If `n` is negative or not an integer.
606
+ */
607
+ function factorial(n, asBigInt = false) {
608
+ if (!Number.isInteger(n) || n < 0) throw new RangeError("factorial() expects a non-negative integer");
609
+ if (asBigInt) {
610
+ let result = 1n;
611
+ for (let i = 2n; i <= BigInt(n); i++) result *= i;
364
612
  return result;
365
- },
613
+ }
614
+ let result = 1;
615
+ for (let i = 2; i <= n; i++) result *= i;
616
+ return result;
617
+ }
618
+
619
+ /**
620
+ * Is it even?
621
+ *
622
+ * @param {number} value
623
+ * @returns {boolean}
624
+ */
625
+ function isEven(value) {
626
+ return value % 2 === 0;
627
+ }
628
+
629
+ /**
630
+ * Is it odd? Negative numbers work too.
631
+ *
632
+ * @param {number} value
633
+ * @returns {boolean}
634
+ */
635
+ function isOdd(value) {
636
+ return Math.abs(value % 2) === 1;
637
+ }
638
+
639
+ /**
640
+ * Numbers from `start` up to `end` (excluded). With one argument, counts
641
+ * from 0. Goes down when `end` is smaller.
642
+ *
643
+ * @example
644
+ * num.range(4); // [0, 1, 2, 3]
645
+ * num.range(0, 10, 2); // [0, 2, 4, 6, 8]
646
+ * num.range(5, 0); // [5, 4, 3, 2, 1]
647
+ *
648
+ * @param {number} start
649
+ * @param {number} [end]
650
+ * @param {number} [step] Defaults to `1`, or `-1` when counting down.
651
+ * @returns {number[]}
652
+ */
653
+ function range(start, end, step) {
654
+ let from = start;
655
+ let to = end;
656
+ if (to === undefined) {
657
+ to = from;
658
+ from = 0;
659
+ }
660
+ const inc = step ?? (to < from ? -1 : 1);
661
+ if (inc === 0 || (inc > 0 && from > to) || (inc < 0 && from < to)) return [];
662
+ const decimals = Math.max(_decimalsOf(inc), _decimalsOf(from));
663
+ // Rounding the ratio keeps 1.1 / 0.1 = 11.000000000000002 from adding an item.
664
+ const count = Math.ceil(round((to - from) / inc, 9));
665
+ if (!Number.isFinite(count) || count > 0xffffffff) throw new RangeError("range() would produce too many numbers");
666
+ /** @type {number[]} */
667
+ const out = new Array(count);
668
+ for (let i = 0; i < count; i++) out[i] = decimals ? round(from + i * inc, decimals) : from + i * inc;
669
+ return out;
670
+ }
671
+
672
+ /**
673
+ * Reads a number from anything, ignoring trailing text like units.
674
+ *
675
+ * @example
676
+ * num.parse("42px"); // 42
677
+ * num.parse(" -3.5 kg"); // -3.5
678
+ * num.parse("nope", 0); // 0
679
+ *
680
+ * @param {unknown} value
681
+ * @param {number} [fallback=NaN] Returned when there's no number to read.
682
+ * @returns {number}
683
+ */
684
+ function parse(value, fallback = NaN) {
685
+ if (typeof value === "number") return Number.isNaN(value) ? fallback : value;
686
+ if (typeof value === "bigint") return Number(value);
687
+ const match = /[-+]?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?/i.exec(String(value));
688
+ const parsed = match ? Number(match[0]) : NaN;
689
+ return Number.isNaN(parsed) ? fallback : parsed;
690
+ }
691
+
692
+ /**
693
+ * Turns a size like `"10 MB"` into bytes. The opposite of `formatBytes()`.
694
+ *
695
+ * @example
696
+ * num.parseBytes("1.5 KB"); // 1536
697
+ * num.parseBytes("2 GiB"); // 2147483648
698
+ * num.parseBytes("1 KB", { binary: false }); // 1000
699
+ *
700
+ * @param {string | number} value A size like `"512kb"` or `"1.5G"`, or a number of bytes.
701
+ * @param {ParseBytesOptions} [options]
702
+ * @returns {number} Bytes, or `NaN` if it can't be read.
703
+ */
704
+ function parseBytes(value, options = {}) {
705
+ if (typeof value === "number") return value;
706
+ const match = /^\s*([-+]?(?:\d+\.?\d*|\.\d+))\s*([kmgtpezy]?)(i?)(b?)(?:ytes?)?\s*$/i.exec(String(value));
707
+ if (!match) return NaN;
708
+ const [, amount, prefix, iec] = match;
709
+ const base = iec || options.binary !== false ? 1024 : 1000;
710
+ const power = prefix ? "kmgtpezy".indexOf(prefix.toLowerCase()) + 1 : 0;
711
+ return Math.floor(Number(amount) * base ** power);
712
+ }
713
+
714
+ /**
715
+ * Formats a byte count as a readable size.
716
+ *
717
+ * @example
718
+ * num.formatBytes(1536); // "1.5 KB"
719
+ * num.formatBytes(1000, { binary: false }); // "1 KB"
720
+ * num.formatBytes(1536, { locale: "fr", units: ["o", "Ko", "Mo", "Go"] }); // "1,5 Ko"
721
+ *
722
+ * @param {number} bytes
723
+ * @param {FormatBytesOptions} [options]
724
+ * @returns {string}
725
+ */
726
+ function formatBytes(bytes, options = {}) {
727
+ const units = options.units ?? (options.iec ? _IEC_UNITS : _SIZE_UNITS);
728
+ if (!Number.isFinite(bytes) || bytes === 0) return `0 ${units[0]}`;
729
+ const decimals = options.decimals ?? 1;
730
+ const base = options.binary === false ? 1000 : 1024;
731
+ const last = units.length - 1;
732
+ let amount = Math.abs(bytes);
733
+ let unit = 0;
734
+ while (amount >= base && unit < last) {
735
+ amount /= base;
736
+ unit++;
737
+ }
738
+ amount = round(amount, decimals);
739
+ // Rounding can reach the next unit: 1023.99 KB reads better as 1 MB.
740
+ if (amount >= base && unit < last) {
741
+ amount = round(amount / base, decimals);
742
+ unit++;
743
+ }
744
+ const locale = options.locale ?? (_config.locale === "en" ? undefined : _config.locale);
745
+ const text = locale ? intl.number(locale, { maximumFractionDigits: decimals }).format(amount) : String(amount);
746
+ return `${bytes < 0 ? "-" : ""}${text} ${units[unit]}`;
747
+ }
748
+
749
+ /**
750
+ * Formats a number for display, with `Intl.NumberFormat` doing the work:
751
+ * separators, decimals, compact notation, percentages, units.
752
+ *
753
+ * @example
754
+ * num.format(1234567.891); // "1,234,567.891"
755
+ * num.format(1234567.891, { locale: "fr" }); // "1 234 567,891"
756
+ * num.format(0.256, { style: "percent" }); // "26%"
757
+ * num.format(1500, { compact: true }); // "1.5K"
758
+ * num.format(3.5, { unit: "kilometer" }); // "3.5 km"
759
+ *
760
+ * @param {number | bigint} value
761
+ * @param {FormatNumberOptions} [options]
762
+ * @returns {string}
763
+ */
764
+ function format(value, options = {}) {
765
+ /** @type {Intl.NumberFormatOptions} */
766
+ const format = {};
767
+ if (options.decimals !== undefined) {
768
+ format.minimumFractionDigits = options.decimals;
769
+ format.maximumFractionDigits = options.decimals;
770
+ }
771
+ if (options.minDecimals !== undefined) format.minimumFractionDigits = options.minDecimals;
772
+ if (options.maxDecimals !== undefined) format.maximumFractionDigits = options.maxDecimals;
773
+ if (options.compact) {
774
+ format.notation = "compact";
775
+ format.compactDisplay = "short";
776
+ }
777
+ if (options.style === "percent") format.style = "percent";
778
+ if (options.unit) {
779
+ format.style = "unit";
780
+ format.unit = options.unit;
781
+ format.unitDisplay = options.unitDisplay ?? "short";
782
+ }
783
+ if (options.signDisplay) format.signDisplay = options.signDisplay;
784
+ if (options.grouping === false) format.useGrouping = false;
785
+ return intl.number(options.locale ?? _config.locale, format).format(value);
786
+ }
787
+
788
+ /**
789
+ * Formats an amount of money.
790
+ *
791
+ * @example
792
+ * num.currency(1234.5, "USD"); // "$1,234.50"
793
+ * num.currency(1234.5, "EUR", { locale: "fr" }); // "1 234,50 €"
794
+ * num.currency(-5, "USD", { accounting: true }); // "($5.00)"
795
+ *
796
+ * @param {number | bigint} value
797
+ * @param {string} code A currency code like `"EUR"`, `"USD"` or `"JPY"`.
798
+ * @param {CurrencyOptions} [options]
799
+ * @returns {string}
800
+ */
801
+ function currency(value, code, options = {}) {
802
+ /** @type {Intl.NumberFormatOptions} */
803
+ const format = { style: "currency", currency: code, currencyDisplay: options.display ?? "symbol" };
804
+ if (options.decimals !== undefined) {
805
+ format.minimumFractionDigits = options.decimals;
806
+ format.maximumFractionDigits = options.decimals;
807
+ }
808
+ if (options.accounting) format.currencySign = "accounting";
809
+ return intl.number(options.locale ?? _config.locale, format).format(value);
810
+ }
811
+
812
+ /**
813
+ * Shortens big numbers with `K`, `M`, `B` or `T`. For other languages, use
814
+ * `format(value, { compact: true, locale })`.
815
+ *
816
+ * @example
817
+ * num.abbreviate(1500); // "1.5K"
818
+ * num.abbreviate(2_400_000); // "2.4M"
819
+ *
820
+ * @param {number} value
821
+ * @param {number} [decimals=1]
822
+ * @returns {string}
823
+ */
824
+ function abbreviate(value, decimals = 1) {
825
+ if (!Number.isFinite(value)) return String(value);
826
+ const sign = value < 0 ? "-" : "";
827
+ const abs = Math.abs(value);
828
+ for (let i = 0; i < _ABBREVIATIONS.length; i++) {
829
+ const [limit, suffix] = _ABBREVIATIONS[i];
830
+ if (abs < limit) continue;
831
+ const rounded = round(abs / limit, decimals);
832
+ // Rounding can reach the next suffix: 999,950 is 1M, not 1000K.
833
+ if (rounded >= 1000 && i > 0) return `${sign}${round(abs / _ABBREVIATIONS[i - 1][0], decimals)}${_ABBREVIATIONS[i - 1][1]}`;
834
+ return `${sign}${rounded}${suffix}`;
835
+ }
836
+ const rounded = round(abs, decimals);
837
+ return rounded >= 1000 ? `${sign}${round(abs / 1000, decimals)}K` : `${sign}${rounded}`;
838
+ }
839
+
840
+ /**
841
+ * Adds thousands separators, leaving decimals alone.
842
+ *
843
+ * @example
844
+ * num.thousands(1234567.89, " "); // "1 234 567.89"
845
+ *
846
+ * @param {number | string} value
847
+ * @param {string} [separator=","]
848
+ * @returns {string}
849
+ */
850
+ function thousands(value, separator = ",") {
851
+ const [int, dec] = String(value).split(".");
852
+ const grouped = int.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
853
+ return dec === undefined ? grouped : `${grouped}.${dec}`;
854
+ }
855
+
856
+ /**
857
+ * Pads the integer part with zeros (or another character).
858
+ *
859
+ * @example
860
+ * num.pad(7, 3); // "007"
861
+ * num.pad(-7, 3); // "-007"
862
+ * num.pad(3.5, 2); // "03.5"
863
+ *
864
+ * @param {number} value
865
+ * @param {number} length Digits wanted before the decimal point.
866
+ * @param {string} [char="0"]
867
+ * @returns {string}
868
+ */
869
+ function pad(value, length, char = "0") {
870
+ const [int, dec] = String(Math.abs(value)).split(".");
871
+ const sign = value < 0 || Object.is(value, -0) ? "-" : "";
872
+ return `${sign}${int.padStart(length, char)}${dec === undefined ? "" : `.${dec}`}`;
873
+ }
874
+
875
+ /**
876
+ * Adds the ordinal suffix: 1st, 2nd, 3rd... in several languages.
877
+ *
878
+ * @example
879
+ * num.ordinal(22); // "22nd"
880
+ * num.ordinal(1, { locale: "fr" }); // "1er"
881
+ * num.ordinal(3, { locale: "de" }); // "3."
882
+ *
883
+ * @param {number} value
884
+ * @param {OrdinalOptions} [options]
885
+ * @returns {string}
886
+ */
887
+ function ordinal(value, options = {}) {
888
+ const locale = options.locale ?? _config.locale;
889
+ const language = locale.split(/[-_]/)[0].toLowerCase();
890
+ const suffixes = options.suffixes ?? _ORDINAL_SUFFIXES[language] ?? _ORDINAL_SUFFIXES.en;
891
+ let category;
892
+ try {
893
+ category = intl.plural(locale, _ORDINAL).select(value);
894
+ } catch {
895
+ category = intl.plural("en", _ORDINAL).select(value);
896
+ }
897
+ return `${value}${suffixes[category] ?? suffixes.other ?? ""}`;
898
+ }
899
+
900
+ /**
901
+ * Sets the default language of `format`, `currency`, `ordinal` and
902
+ * `formatBytes`.
903
+ *
904
+ * @example
905
+ * num.setLocale("fr");
906
+ * num.currency(1234.5, "EUR"); // "1 234,50 €"
907
+ *
908
+ * @param {string} locale Like `"fr"`, `"en-GB"` or `"pt-BR"`.
909
+ * @returns {typeof import("./Num")} The module, so calls chain.
910
+ */
911
+ function setLocale(locale) {
912
+ _config.locale = locale;
913
+ return module.exports;
914
+ }
915
+
916
+ /**
917
+ * The current default language.
918
+ *
919
+ * @example
920
+ * num.getLocale(); // "en"
921
+ *
922
+ * @returns {string}
923
+ */
924
+ function getLocale() {
925
+ return _config.locale;
926
+ }
927
+
928
+ module.exports = {
929
+ setLocale,
930
+ getLocale,
931
+ clamp,
932
+ inRange,
933
+ wrap,
934
+ round,
935
+ floor,
936
+ ceil,
937
+ snap,
938
+ approxEqual,
939
+ lerp,
940
+ mapRange,
941
+ random,
942
+ randomInt,
943
+ sum,
944
+ product,
945
+ average,
946
+ median,
947
+ mode,
948
+ variance,
949
+ stdDev,
950
+ percentile,
951
+ min,
952
+ max,
953
+ percent,
954
+ percentChange,
955
+ gcd,
956
+ lcm,
957
+ isPrime,
958
+ factorial,
959
+ isEven,
960
+ isOdd,
961
+ range,
962
+ parse,
963
+ parseBytes,
964
+ formatBytes,
965
+ format,
966
+ currency,
967
+ abbreviate,
968
+ thousands,
969
+ pad,
970
+ ordinal,
366
971
  };