xypriss 9.13.13 → 9.13.15
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.
- package/dist/cjs/src/xhsc/UtilsApi.js +21 -1
- package/dist/cjs/src/xhsc/UtilsApi.js.map +1 -1
- package/dist/cjs/src/xhsc/api/env/XessIpcClient.js +73 -2
- package/dist/cjs/src/xhsc/api/env/XessIpcClient.js.map +1 -1
- package/dist/cjs/src/xhsc/utils/NumberUtils.js +1129 -26
- package/dist/cjs/src/xhsc/utils/NumberUtils.js.map +1 -1
- package/dist/esm/src/xhsc/UtilsApi.js +21 -1
- package/dist/esm/src/xhsc/UtilsApi.js.map +1 -1
- package/dist/esm/src/xhsc/api/env/XessIpcClient.js +73 -2
- package/dist/esm/src/xhsc/api/env/XessIpcClient.js.map +1 -1
- package/dist/esm/src/xhsc/utils/NumberUtils.js +1129 -26
- package/dist/esm/src/xhsc/utils/NumberUtils.js.map +1 -1
- package/dist/index.d.ts +820 -19
- package/package.json +1 -1
|
@@ -1,75 +1,1178 @@
|
|
|
1
|
+
import { Random } from 'xypriss-security';
|
|
2
|
+
|
|
3
|
+
/***************************************************************************
|
|
4
|
+
* XyPrissJS - Fast And Secure
|
|
5
|
+
*
|
|
6
|
+
* @author Nehonix
|
|
7
|
+
* @license Nehonix OSL (NOSL)
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2025 Nehonix. All rights reserved.
|
|
10
|
+
*
|
|
11
|
+
* This License governs the use, modification, and distribution of software
|
|
12
|
+
* provided by NEHONIX under its open source projects.
|
|
13
|
+
* NEHONIX is committed to fostering collaborative innovation while strictly
|
|
14
|
+
* protecting its intellectual property rights.
|
|
15
|
+
* Violation of any term of this License will result in immediate termination of all granted rights
|
|
16
|
+
* and may subject the violator to legal action.
|
|
17
|
+
*
|
|
18
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
19
|
+
* INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
|
|
20
|
+
* AND NON-INFRINGEMENT.
|
|
21
|
+
* IN NO EVENT SHALL NEHONIX BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
|
22
|
+
* OR CONSEQUENTIAL DAMAGES ARISING FROM THE USE OR INABILITY TO USE THE SOFTWARE,
|
|
23
|
+
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
|
24
|
+
*
|
|
25
|
+
***************************************************************************** */
|
|
26
|
+
/**
|
|
27
|
+
* @file NumberUtils.ts
|
|
28
|
+
* @description XyPriss Number & Math Utilities — A comprehensive suite of
|
|
29
|
+
* everyday numeric calculations, file & storage conversions, percentage analytics,
|
|
30
|
+
* pagination algorithms, precision roundings, and localized formatters.
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* All methods are pure and deterministic unless utilizing cryptographic randomness
|
|
34
|
+
* provided by `xypriss-security`.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const num = new NumberUtils();
|
|
39
|
+
*
|
|
40
|
+
* // File & Storage Calculations
|
|
41
|
+
* num.formatBytes(15_728_640); // "15 MB"
|
|
42
|
+
* num.parseBytes("25.5 MB"); // 26738688
|
|
43
|
+
* num.toMB(104857600); // 100
|
|
44
|
+
*
|
|
45
|
+
* // Daily Percentages & Ratios
|
|
46
|
+
* num.percentage(45, 200); // 22.5 (%)
|
|
47
|
+
* num.percentageOf(20, 150); // 30
|
|
48
|
+
* num.percentageChange(100, 125); // 25 (+25%)
|
|
49
|
+
*
|
|
50
|
+
* // Pagination Helper
|
|
51
|
+
* num.paginate(154, 20, 2); // { currentPage: 2, totalPages: 8, offset: 20, limit: 20, ... }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
1
54
|
/**
|
|
2
55
|
* **NumberUtils — XyPriss Number & Math Utilities**
|
|
56
|
+
*
|
|
57
|
+
* Provides ready-to-use helpers for daily mathematical operations, data storage units,
|
|
58
|
+
* rates, roundings, statistical metrics, bounds, and formatted output.
|
|
3
59
|
*/
|
|
4
60
|
class NumberUtils {
|
|
61
|
+
// ─────────────────────────────────────────────
|
|
62
|
+
// Internal Helpers
|
|
63
|
+
// ─────────────────────────────────────────────
|
|
64
|
+
/**
|
|
65
|
+
* Flattens a variadic list of numbers or number arrays into a flat numeric array.
|
|
66
|
+
*
|
|
67
|
+
* @param args - Numbers or arrays of numbers.
|
|
68
|
+
* @returns A flat array of valid numbers.
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
flattenNumbers(args) {
|
|
72
|
+
const result = [];
|
|
73
|
+
for (const item of args) {
|
|
74
|
+
if (Array.isArray(item)) {
|
|
75
|
+
for (const num of item) {
|
|
76
|
+
if (typeof num === "number" && !isNaN(num)) {
|
|
77
|
+
result.push(num);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (typeof item === "number" && !isNaN(item)) {
|
|
82
|
+
result.push(item);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
// ─────────────────────────────────────────────
|
|
88
|
+
// File Sizes & Storage Data Calculations
|
|
89
|
+
// ─────────────────────────────────────────────
|
|
90
|
+
/**
|
|
91
|
+
* **Format Bytes**
|
|
92
|
+
*
|
|
93
|
+
* Converts a raw number of bytes into a human-readable size string (e.g., `KB`, `MB`, `GB` or `KiB`, `MiB`, `GiB`).
|
|
94
|
+
* Supports negative values and configurable decimal precision.
|
|
95
|
+
*
|
|
96
|
+
* @param bytes - The number of bytes to format.
|
|
97
|
+
* @param optionsOrDecimals - Number of decimal places (default: `2`) or a {@link FormatBytesOptions} configuration object.
|
|
98
|
+
* @returns A human-readable file size string.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* num.formatBytes(0); // "0 Bytes"
|
|
103
|
+
* num.formatBytes(1024); // "1 KB"
|
|
104
|
+
* num.formatBytes(15728640, 1); // "15 MB"
|
|
105
|
+
* num.formatBytes(1073741824, { binary: true }); // "1 GiB"
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
formatBytes(bytes, optionsOrDecimals = 2) {
|
|
109
|
+
const options = typeof optionsOrDecimals === "number"
|
|
110
|
+
? { decimals: optionsOrDecimals }
|
|
111
|
+
: optionsOrDecimals || {};
|
|
112
|
+
const decimals = options.decimals !== undefined && options.decimals >= 0
|
|
113
|
+
? options.decimals
|
|
114
|
+
: 2;
|
|
115
|
+
const binary = options.binary ?? false;
|
|
116
|
+
const spacer = options.spacer ?? " ";
|
|
117
|
+
if (bytes === 0) {
|
|
118
|
+
return `0${spacer}Bytes`;
|
|
119
|
+
}
|
|
120
|
+
const isNegative = bytes < 0;
|
|
121
|
+
const absBytes = Math.abs(bytes);
|
|
122
|
+
const k = 1024;
|
|
123
|
+
const standardSizes = [
|
|
124
|
+
"Bytes",
|
|
125
|
+
"KB",
|
|
126
|
+
"MB",
|
|
127
|
+
"GB",
|
|
128
|
+
"TB",
|
|
129
|
+
"PB",
|
|
130
|
+
"EB",
|
|
131
|
+
"ZB",
|
|
132
|
+
"YB",
|
|
133
|
+
];
|
|
134
|
+
const binarySizes = [
|
|
135
|
+
"Bytes",
|
|
136
|
+
"KiB",
|
|
137
|
+
"MiB",
|
|
138
|
+
"GiB",
|
|
139
|
+
"TiB",
|
|
140
|
+
"PiB",
|
|
141
|
+
"EiB",
|
|
142
|
+
"ZiB",
|
|
143
|
+
"YiB",
|
|
144
|
+
];
|
|
145
|
+
const sizes = binary ? binarySizes : standardSizes;
|
|
146
|
+
const i = Math.min(Math.floor(Math.log(absBytes) / Math.log(k)), sizes.length - 1);
|
|
147
|
+
if (i === 0) {
|
|
148
|
+
return `${isNegative ? "-" : ""}${absBytes}${spacer}${sizes[0]}`;
|
|
149
|
+
}
|
|
150
|
+
const val = absBytes / Math.pow(k, i);
|
|
151
|
+
const formatted = this.round(val, decimals);
|
|
152
|
+
return `${isNegative ? "-" : ""}${formatted}${spacer}${sizes[i]}`;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* **Parse Bytes**
|
|
156
|
+
*
|
|
157
|
+
* Parses a human-readable storage string (e.g., `"10MB"`, `"1.5 GB"`, `"500KB"`, `"64KiB"`, `"1024 B"`)
|
|
158
|
+
* into the equivalent raw number of bytes. Very helpful for upload limits, payload sizes, and cache config.
|
|
159
|
+
*
|
|
160
|
+
* @param input - The size string to parse or raw byte number.
|
|
161
|
+
* @returns The parsed number of bytes, or `NaN` if parsing fails.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* num.parseBytes("500 KB"); // 512000
|
|
166
|
+
* num.parseBytes("10MB"); // 10485760
|
|
167
|
+
* num.parseBytes("1.5 GB"); // 1610612736
|
|
168
|
+
* num.parseBytes("256 B"); // 256
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
parseBytes(input) {
|
|
172
|
+
if (typeof input === "number") {
|
|
173
|
+
return isNaN(input) ? 0 : input;
|
|
174
|
+
}
|
|
175
|
+
if (!input || typeof input !== "string") {
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
const trimmed = input.trim();
|
|
179
|
+
const match = trimmed.match(/^([+-]?(?:\d+(?:\.\d+)?|\.\d+))\s*([a-zA-Z]*)$/);
|
|
180
|
+
if (!match) {
|
|
181
|
+
return NaN;
|
|
182
|
+
}
|
|
183
|
+
const value = parseFloat(match[1]);
|
|
184
|
+
const unit = match[2].toUpperCase();
|
|
185
|
+
if (!unit || unit === "B" || unit === "BYTES" || unit === "BYTE") {
|
|
186
|
+
return Math.round(value);
|
|
187
|
+
}
|
|
188
|
+
const multipliers = {
|
|
189
|
+
K: 1024,
|
|
190
|
+
KB: 1024,
|
|
191
|
+
KIB: 1024,
|
|
192
|
+
M: 1024 ** 2,
|
|
193
|
+
MB: 1024 ** 2,
|
|
194
|
+
MIB: 1024 ** 2,
|
|
195
|
+
G: 1024 ** 3,
|
|
196
|
+
GB: 1024 ** 3,
|
|
197
|
+
GIB: 1024 ** 3,
|
|
198
|
+
T: 1024 ** 4,
|
|
199
|
+
TB: 1024 ** 4,
|
|
200
|
+
TIB: 1024 ** 4,
|
|
201
|
+
P: 1024 ** 5,
|
|
202
|
+
PB: 1024 ** 5,
|
|
203
|
+
PIB: 1024 ** 5,
|
|
204
|
+
};
|
|
205
|
+
const mult = multipliers[unit];
|
|
206
|
+
return mult !== undefined ? Math.round(value * mult) : NaN;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* **Convert Bytes to Kilobytes (KB)**
|
|
210
|
+
*
|
|
211
|
+
* @param bytes - Number of raw bytes.
|
|
212
|
+
* @param decimals - Decimal places (default: `2`).
|
|
213
|
+
* @returns The value in Kilobytes.
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```ts
|
|
217
|
+
* num.toKB(2048); // 2
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
toKB(bytes, decimals = 2) {
|
|
221
|
+
return this.round(bytes / 1024, decimals);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* **Convert Bytes to Megabytes (MB)**
|
|
225
|
+
*
|
|
226
|
+
* @param bytes - Number of raw bytes.
|
|
227
|
+
* @param decimals - Decimal places (default: `2`).
|
|
228
|
+
* @returns The value in Megabytes.
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```ts
|
|
232
|
+
* num.toMB(10485760); // 10
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
toMB(bytes, decimals = 2) {
|
|
236
|
+
return this.round(bytes / 1024 ** 2, decimals);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* **Convert Bytes to Gigabytes (GB)**
|
|
240
|
+
*
|
|
241
|
+
* @param bytes - Number of raw bytes.
|
|
242
|
+
* @param decimals - Decimal places (default: `2`).
|
|
243
|
+
* @returns The value in Gigabytes.
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```ts
|
|
247
|
+
* num.toGB(1073741824); // 1
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
toGB(bytes, decimals = 2) {
|
|
251
|
+
return this.round(bytes / 1024 ** 3, decimals);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* **Convert Bytes to Terabytes (TB)**
|
|
255
|
+
*
|
|
256
|
+
* @param bytes - Number of raw bytes.
|
|
257
|
+
* @param decimals - Decimal places (default: `2`).
|
|
258
|
+
* @returns The value in Terabytes.
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* num.toTB(1099511627776); // 1
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
toTB(bytes, decimals = 2) {
|
|
266
|
+
return this.round(bytes / 1024 ** 4, decimals);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* **Convert Bytes to Target Unit**
|
|
270
|
+
*
|
|
271
|
+
* Converts a byte count to any specified target unit (`KB`, `MB`, `GB`, `TB`, etc.).
|
|
272
|
+
*
|
|
273
|
+
* @param bytes - The number of bytes.
|
|
274
|
+
* @param targetUnit - Destination unit (e.g., `"MB"`, `"GB"`).
|
|
275
|
+
* @param decimals - Decimal places (default: `2`).
|
|
276
|
+
* @returns The converted numeric value.
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* ```ts
|
|
280
|
+
* num.convertBytes(52428800, "MB"); // 50
|
|
281
|
+
* ```
|
|
282
|
+
*/
|
|
283
|
+
convertBytes(bytes, targetUnit, decimals = 2) {
|
|
284
|
+
const unitMap = {
|
|
285
|
+
B: 1,
|
|
286
|
+
KB: 1024,
|
|
287
|
+
KIB: 1024,
|
|
288
|
+
MB: 1024 ** 2,
|
|
289
|
+
MIB: 1024 ** 2,
|
|
290
|
+
GB: 1024 ** 3,
|
|
291
|
+
GIB: 1024 ** 3,
|
|
292
|
+
TB: 1024 ** 4,
|
|
293
|
+
TIB: 1024 ** 4,
|
|
294
|
+
PB: 1024 ** 5,
|
|
295
|
+
PIB: 1024 ** 5,
|
|
296
|
+
EB: 1024 ** 6,
|
|
297
|
+
EIB: 1024 ** 6,
|
|
298
|
+
ZB: 1024 ** 7,
|
|
299
|
+
ZIB: 1024 ** 7,
|
|
300
|
+
YB: 1024 ** 8,
|
|
301
|
+
YIB: 1024 ** 8,
|
|
302
|
+
};
|
|
303
|
+
const divisor = unitMap[targetUnit.toUpperCase()] ?? 1;
|
|
304
|
+
return this.round(bytes / divisor, decimals);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* **Transfer Rate / Speed**
|
|
308
|
+
*
|
|
309
|
+
* Calculates the transfer throughput given transferred bytes and elapsed duration in milliseconds.
|
|
310
|
+
*
|
|
311
|
+
* @param bytes - Transferred bytes.
|
|
312
|
+
* @param durationMs - Duration in milliseconds.
|
|
313
|
+
* @param decimals - Decimal places (default: `2`).
|
|
314
|
+
* @returns A formatted speed string (e.g., `"12.5 MB/s"`).
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* num.transferRate(10485760, 2000); // "5 MB/s"
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
transferRate(bytes, durationMs, decimals = 2) {
|
|
322
|
+
if (durationMs <= 0 || bytes <= 0) {
|
|
323
|
+
return "0 Bytes/s";
|
|
324
|
+
}
|
|
325
|
+
const bytesPerSec = (bytes / durationMs) * 1000;
|
|
326
|
+
return `${this.formatBytes(bytesPerSec, decimals)}/s`;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* **Transfer Estimated Time of Arrival (ETA)**
|
|
330
|
+
*
|
|
331
|
+
* Calculates the remaining time in seconds to complete a file or data transfer.
|
|
332
|
+
*
|
|
333
|
+
* @param remainingBytes - Bytes left to transfer.
|
|
334
|
+
* @param bytesPerSec - Transfer throughput in bytes per second.
|
|
335
|
+
* @returns Remaining time in seconds (rounded up), or `0` if invalid.
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```ts
|
|
339
|
+
* num.transferETA(52428800, 10485760); // 5 (seconds)
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
transferETA(remainingBytes, bytesPerSec) {
|
|
343
|
+
if (bytesPerSec <= 0 || remainingBytes <= 0) {
|
|
344
|
+
return 0;
|
|
345
|
+
}
|
|
346
|
+
return Math.ceil(remainingBytes / bytesPerSec);
|
|
347
|
+
}
|
|
348
|
+
// ─────────────────────────────────────────────
|
|
349
|
+
// Percentages, Ratios & Progress
|
|
350
|
+
// ─────────────────────────────────────────────
|
|
351
|
+
/**
|
|
352
|
+
* **Calculate Percentage (Partial of Total)**
|
|
353
|
+
*
|
|
354
|
+
* Returns what percentage `partial` represents relative to `total`: `(partial / total) * 100`.
|
|
355
|
+
* Safely returns `0` if `total === 0` to prevent division by zero.
|
|
356
|
+
*
|
|
357
|
+
* @param partial - The partial amount.
|
|
358
|
+
* @param total - The reference total.
|
|
359
|
+
* @param decimals - Decimal places (default: `2`).
|
|
360
|
+
* @returns The percentage value (e.g. `25` for 25%).
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* ```ts
|
|
364
|
+
* num.percentage(25, 200); // 12.5
|
|
365
|
+
* num.percentage(50, 100); // 50
|
|
366
|
+
* num.percentage(0, 0); // 0
|
|
367
|
+
* ```
|
|
368
|
+
*/
|
|
369
|
+
percentage(partial, total, decimals = 2) {
|
|
370
|
+
if (total === 0) {
|
|
371
|
+
return 0;
|
|
372
|
+
}
|
|
373
|
+
return this.round((partial / total) * 100, decimals);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* **Calculate Value From Percentage (X% of Total)**
|
|
377
|
+
*
|
|
378
|
+
* Returns the actual value corresponding to `percent` of `total`: `(percent / 100) * total`.
|
|
379
|
+
*
|
|
380
|
+
* @param percent - The percentage rate (e.g., `20` for 20%).
|
|
381
|
+
* @param total - The reference total.
|
|
382
|
+
* @param decimals - Decimal places (default: `2`).
|
|
383
|
+
* @returns The calculated portion.
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```ts
|
|
387
|
+
* num.percentageOf(20, 150); // 30 (20% of 150 is 30)
|
|
388
|
+
* num.percentageOf(5.5, 200); // 11
|
|
389
|
+
* ```
|
|
390
|
+
*/
|
|
391
|
+
percentageOf(percent, total, decimals = 2) {
|
|
392
|
+
return this.round((percent / 100) * total, decimals);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* **Percentage Change / Growth Rate**
|
|
396
|
+
*
|
|
397
|
+
* Computes the relative percentage variation from `oldValue` to `newValue`.
|
|
398
|
+
* Positive indicates an increase, negative indicates a decrease.
|
|
399
|
+
*
|
|
400
|
+
* @param oldValue - The baseline value.
|
|
401
|
+
* @param newValue - The current value.
|
|
402
|
+
* @param decimals - Decimal precision (default: `2`).
|
|
403
|
+
* @returns Percentage variation.
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```ts
|
|
407
|
+
* num.percentageChange(100, 150); // 50 (+50%)
|
|
408
|
+
* num.percentageChange(200, 150); // -25 (-25%)
|
|
409
|
+
* ```
|
|
410
|
+
*/
|
|
411
|
+
percentageChange(oldValue, newValue, decimals = 2) {
|
|
412
|
+
if (oldValue === 0) {
|
|
413
|
+
return newValue === 0 ? 0 : 100;
|
|
414
|
+
}
|
|
415
|
+
const change = ((newValue - oldValue) / Math.abs(oldValue)) * 100;
|
|
416
|
+
return this.round(change, decimals);
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* **Progress Ratio**
|
|
420
|
+
*
|
|
421
|
+
* Calculates a normalized progress value clamped strictly between `0.0` and `1.0`.
|
|
422
|
+
* Ideal for progress bars and completion meters.
|
|
423
|
+
*
|
|
424
|
+
* @param current - The current progress value.
|
|
425
|
+
* @param total - The target total value.
|
|
426
|
+
* @returns A float between `0.0` and `1.0`.
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```ts
|
|
430
|
+
* num.progress(50, 100); // 0.5
|
|
431
|
+
* num.progress(120, 100); // 1.0 (clamped)
|
|
432
|
+
* num.progress(-10, 100); // 0.0 (clamped)
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
435
|
+
progress(current, total) {
|
|
436
|
+
if (total <= 0) {
|
|
437
|
+
return 0;
|
|
438
|
+
}
|
|
439
|
+
return this.clamp(current / total, 0, 1);
|
|
440
|
+
}
|
|
441
|
+
// ─────────────────────────────────────────────
|
|
442
|
+
// Rounding & Decimal Precision
|
|
443
|
+
// ─────────────────────────────────────────────
|
|
444
|
+
/**
|
|
445
|
+
* **Accurate Decimal Rounding**
|
|
446
|
+
*
|
|
447
|
+
* Accurately rounds a number to a specified number of decimal places.
|
|
448
|
+
* Uses exponential notation internally to avoid common JavaScript floating-point rounding bugs (e.g. `1.005 * 100`).
|
|
449
|
+
*
|
|
450
|
+
* @param value - The number to round.
|
|
451
|
+
* @param decimals - Decimal places (default: `0`).
|
|
452
|
+
* @returns The rounded number.
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* ```ts
|
|
456
|
+
* num.round(1.005, 2); // 1.01 (avoids standard JS Math.round bug)
|
|
457
|
+
* num.round(12.3456, 2); // 12.35
|
|
458
|
+
* num.round(12.3456, 0); // 12
|
|
459
|
+
* ```
|
|
460
|
+
*/
|
|
461
|
+
round(value, decimals = 0) {
|
|
462
|
+
if (isNaN(value)) {
|
|
463
|
+
return NaN;
|
|
464
|
+
}
|
|
465
|
+
if (decimals <= 0) {
|
|
466
|
+
return Math.round(value);
|
|
467
|
+
}
|
|
468
|
+
return Number(Math.round(Number(value + "e" + decimals)) + "e-" + decimals);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* **Accurate Decimal Ceil**
|
|
472
|
+
*
|
|
473
|
+
* Rounds up to the nearest specified decimal precision.
|
|
474
|
+
*
|
|
475
|
+
* @param value - The number to round up.
|
|
476
|
+
* @param decimals - Decimal places (default: `0`).
|
|
477
|
+
* @returns Ceiled value.
|
|
478
|
+
*
|
|
479
|
+
* @example
|
|
480
|
+
* ```ts
|
|
481
|
+
* num.ceil(1.234, 2); // 1.24
|
|
482
|
+
* num.ceil(1.2, 0); // 2
|
|
483
|
+
* ```
|
|
484
|
+
*/
|
|
485
|
+
ceil(value, decimals = 0) {
|
|
486
|
+
if (isNaN(value)) {
|
|
487
|
+
return NaN;
|
|
488
|
+
}
|
|
489
|
+
if (decimals <= 0) {
|
|
490
|
+
return Math.ceil(value);
|
|
491
|
+
}
|
|
492
|
+
return Number(Math.ceil(Number(value + "e" + decimals)) + "e-" + decimals);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* **Accurate Decimal Floor**
|
|
496
|
+
*
|
|
497
|
+
* Rounds down to the nearest specified decimal precision.
|
|
498
|
+
*
|
|
499
|
+
* @param value - The number to round down.
|
|
500
|
+
* @param decimals - Decimal places (default: `0`).
|
|
501
|
+
* @returns Floored value.
|
|
502
|
+
*
|
|
503
|
+
* @example
|
|
504
|
+
* ```ts
|
|
505
|
+
* num.floor(1.239, 2); // 1.23
|
|
506
|
+
* num.floor(1.9, 0); // 1
|
|
507
|
+
* ```
|
|
508
|
+
*/
|
|
509
|
+
floor(value, decimals = 0) {
|
|
510
|
+
if (isNaN(value)) {
|
|
511
|
+
return NaN;
|
|
512
|
+
}
|
|
513
|
+
if (decimals <= 0) {
|
|
514
|
+
return Math.floor(value);
|
|
515
|
+
}
|
|
516
|
+
return Number(Math.floor(Number(value + "e" + decimals)) + "e-" + decimals);
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* **Round to Step / Multiple**
|
|
520
|
+
*
|
|
521
|
+
* Rounds a value to the nearest multiple of a given step (e.g., nearest `5`, `0.25`, or `100`).
|
|
522
|
+
*
|
|
523
|
+
* @param value - The value to round.
|
|
524
|
+
* @param step - The increment step size.
|
|
525
|
+
* @returns Value rounded to the nearest step.
|
|
526
|
+
*
|
|
527
|
+
* @example
|
|
528
|
+
* ```ts
|
|
529
|
+
* num.roundToStep(23, 5); // 25
|
|
530
|
+
* num.roundToStep(1.234, 0.05); // 1.25
|
|
531
|
+
* num.roundToStep(1430, 500); // 1500
|
|
532
|
+
* ```
|
|
533
|
+
*/
|
|
534
|
+
roundToStep(value, step) {
|
|
535
|
+
if (step <= 0) {
|
|
536
|
+
return value;
|
|
537
|
+
}
|
|
538
|
+
const inv = 1 / step;
|
|
539
|
+
return Math.round(value * inv) / inv;
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* **Round to Significant Digits**
|
|
543
|
+
*
|
|
544
|
+
* Formats a number to a specified number of significant digits.
|
|
545
|
+
*
|
|
546
|
+
* @param value - The number to format.
|
|
547
|
+
* @param digits - Number of significant digits.
|
|
548
|
+
* @returns The number rounded to significant precision.
|
|
549
|
+
*
|
|
550
|
+
* @example
|
|
551
|
+
* ```ts
|
|
552
|
+
* num.toPrecision(12345, 3); // 12300
|
|
553
|
+
* num.toPrecision(0.001234, 2); // 0.0012
|
|
554
|
+
* ```
|
|
555
|
+
*/
|
|
556
|
+
toPrecision(value, digits) {
|
|
557
|
+
if (isNaN(value) || digits <= 0) {
|
|
558
|
+
return value;
|
|
559
|
+
}
|
|
560
|
+
return parseFloat(value.toPrecision(digits));
|
|
561
|
+
}
|
|
562
|
+
// ─────────────────────────────────────────────
|
|
563
|
+
// Bounds, Clamping & Mapping
|
|
564
|
+
// ─────────────────────────────────────────────
|
|
5
565
|
/**
|
|
6
566
|
* **Clamp a Value**
|
|
7
567
|
*
|
|
8
|
-
* Restricts a numeric value
|
|
568
|
+
* Restricts a numeric value within a defined range `[min, max]`.
|
|
569
|
+
* If `min > max`, the bounds are automatically swapped for safety.
|
|
9
570
|
*
|
|
10
571
|
* @param value - The value to clamp.
|
|
11
572
|
* @param min - The lower bound.
|
|
12
573
|
* @param max - The upper bound.
|
|
13
574
|
* @returns The clamped value.
|
|
575
|
+
*
|
|
576
|
+
* @example
|
|
577
|
+
* ```ts
|
|
578
|
+
* num.clamp(15, 0, 10); // 10
|
|
579
|
+
* num.clamp(-5, 0, 10); // 0
|
|
580
|
+
* num.clamp(5, 0, 10); // 5
|
|
581
|
+
* ```
|
|
14
582
|
*/
|
|
15
583
|
clamp(value, min, max) {
|
|
16
|
-
|
|
584
|
+
const lower = Math.min(min, max);
|
|
585
|
+
const upper = Math.max(min, max);
|
|
586
|
+
return Math.min(Math.max(value, lower), upper);
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* **Check if Value is in Range**
|
|
590
|
+
*
|
|
591
|
+
* Determines whether a value falls within `[min, max]`.
|
|
592
|
+
*
|
|
593
|
+
* @param value - The value to test.
|
|
594
|
+
* @param min - Range start.
|
|
595
|
+
* @param max - Range end.
|
|
596
|
+
* @param inclusive - Whether the bounds are inclusive (default: `true`).
|
|
597
|
+
* @returns `true` if value is within bounds.
|
|
598
|
+
*
|
|
599
|
+
* @example
|
|
600
|
+
* ```ts
|
|
601
|
+
* num.inRange(5, 1, 10); // true
|
|
602
|
+
* num.inRange(10, 1, 10, true); // true
|
|
603
|
+
* num.inRange(10, 1, 10, false);// false
|
|
604
|
+
* ```
|
|
605
|
+
*/
|
|
606
|
+
inRange(value, min, max, inclusive = true) {
|
|
607
|
+
const lower = Math.min(min, max);
|
|
608
|
+
const upper = Math.max(min, max);
|
|
609
|
+
return inclusive
|
|
610
|
+
? value >= lower && value <= upper
|
|
611
|
+
: value > lower && value < upper;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* **Normalize a Value (0 to 1)**
|
|
615
|
+
*
|
|
616
|
+
* Maps a value from its original range `[min, max]` into a normalized unit float `[0.0, 1.0]`.
|
|
617
|
+
*
|
|
618
|
+
* @param value - The input value.
|
|
619
|
+
* @param min - The minimum of the original range.
|
|
620
|
+
* @param max - The maximum of the original range.
|
|
621
|
+
* @returns Normalized float.
|
|
622
|
+
*
|
|
623
|
+
* @example
|
|
624
|
+
* ```ts
|
|
625
|
+
* num.normalize(50, 0, 100); // 0.5
|
|
626
|
+
* num.normalize(15, 10, 20); // 0.5
|
|
627
|
+
* ```
|
|
628
|
+
*/
|
|
629
|
+
normalize(value, min, max) {
|
|
630
|
+
if (min === max) {
|
|
631
|
+
return 0;
|
|
632
|
+
}
|
|
633
|
+
return (value - min) / (max - min);
|
|
17
634
|
}
|
|
18
635
|
/**
|
|
19
636
|
* **Linear Interpolation (LERP)**
|
|
20
637
|
*
|
|
21
|
-
* Returns the linear interpolation between `start` and `end` for a factor `t
|
|
638
|
+
* Returns the linear interpolation between `start` and `end` for a factor `t`:
|
|
639
|
+
* Formula: `start * (1 - t) + end * t`.
|
|
22
640
|
*
|
|
23
641
|
* @param start - The start value.
|
|
24
642
|
* @param end - The end value.
|
|
25
|
-
* @param t - The interpolation factor (0.0 to 1.0).
|
|
643
|
+
* @param t - The interpolation factor (typically `0.0` to `1.0`).
|
|
26
644
|
* @returns The interpolated value.
|
|
645
|
+
*
|
|
646
|
+
* @example
|
|
647
|
+
* ```ts
|
|
648
|
+
* num.lerp(0, 100, 0.5); // 50
|
|
649
|
+
* num.lerp(20, 80, 0.25); // 35
|
|
650
|
+
* ```
|
|
27
651
|
*/
|
|
28
652
|
lerp(start, end, t) {
|
|
29
653
|
return start * (1 - t) + end * t;
|
|
30
654
|
}
|
|
655
|
+
/**
|
|
656
|
+
* **Map Range (Re-scale)**
|
|
657
|
+
*
|
|
658
|
+
* Maps an input number from an incoming range `[inMin, inMax]` to an outgoing range `[outMin, outMax]`.
|
|
659
|
+
*
|
|
660
|
+
* @param value - The number to map.
|
|
661
|
+
* @param inMin - Lower bound of input range.
|
|
662
|
+
* @param inMax - Upper bound of input range.
|
|
663
|
+
* @param outMin - Lower bound of output range.
|
|
664
|
+
* @param outMax - Upper bound of output range.
|
|
665
|
+
* @returns The re-scaled value.
|
|
666
|
+
*
|
|
667
|
+
* @example
|
|
668
|
+
* ```ts
|
|
669
|
+
* // Map a 0-10 rating to a 0-100 percentage
|
|
670
|
+
* num.mapRange(7.5, 0, 10, 0, 100); // 75
|
|
671
|
+
*
|
|
672
|
+
* // Map temperature sensor (0-1023) to degrees C (0-100)
|
|
673
|
+
* num.mapRange(511.5, 0, 1023, 0, 100); // 50
|
|
674
|
+
* ```
|
|
675
|
+
*/
|
|
676
|
+
mapRange(value, inMin, inMax, outMin, outMax) {
|
|
677
|
+
if (inMin === inMax) {
|
|
678
|
+
return outMin;
|
|
679
|
+
}
|
|
680
|
+
return (((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin);
|
|
681
|
+
}
|
|
682
|
+
// ─────────────────────────────────────────────
|
|
683
|
+
// Aggregates & Statistics
|
|
684
|
+
// ─────────────────────────────────────────────
|
|
685
|
+
/**
|
|
686
|
+
* **Sum of Numbers**
|
|
687
|
+
*
|
|
688
|
+
* Computes the sum of all provided numbers or numeric arrays.
|
|
689
|
+
*
|
|
690
|
+
* @param numbers - Variadic numbers or number arrays.
|
|
691
|
+
* @returns Total sum.
|
|
692
|
+
*
|
|
693
|
+
* @example
|
|
694
|
+
* ```ts
|
|
695
|
+
* num.sum(1, 2, 3, 4); // 10
|
|
696
|
+
* num.sum([10, 20, 30]); // 60
|
|
697
|
+
* ```
|
|
698
|
+
*/
|
|
699
|
+
sum(...numbers) {
|
|
700
|
+
const flat = this.flattenNumbers(numbers);
|
|
701
|
+
return flat.reduce((acc, curr) => acc + curr, 0);
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* **Average / Arithmetic Mean**
|
|
705
|
+
*
|
|
706
|
+
* Computes the arithmetic mean of all provided numbers or numeric arrays.
|
|
707
|
+
*
|
|
708
|
+
* @param numbers - Variadic numbers or number arrays.
|
|
709
|
+
* @returns The average, or `0` if empty.
|
|
710
|
+
*
|
|
711
|
+
* @example
|
|
712
|
+
* ```ts
|
|
713
|
+
* num.average(10, 20, 30); // 20
|
|
714
|
+
* num.average([5, 15]); // 10
|
|
715
|
+
* ```
|
|
716
|
+
*/
|
|
717
|
+
average(...numbers) {
|
|
718
|
+
const flat = this.flattenNumbers(numbers);
|
|
719
|
+
if (flat.length === 0) {
|
|
720
|
+
return 0;
|
|
721
|
+
}
|
|
722
|
+
return this.sum(flat) / flat.length;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* **Median**
|
|
726
|
+
*
|
|
727
|
+
* Returns the statistical median (middle value) of a set of numbers.
|
|
728
|
+
*
|
|
729
|
+
* @param numbers - Variadic numbers or number arrays.
|
|
730
|
+
* @returns The median value, or `0` if empty.
|
|
731
|
+
*
|
|
732
|
+
* @example
|
|
733
|
+
* ```ts
|
|
734
|
+
* num.median(1, 3, 5); // 3
|
|
735
|
+
* num.median([1, 2, 3, 4]); // 2.5
|
|
736
|
+
* ```
|
|
737
|
+
*/
|
|
738
|
+
median(...numbers) {
|
|
739
|
+
const flat = this.flattenNumbers(numbers);
|
|
740
|
+
if (flat.length === 0) {
|
|
741
|
+
return 0;
|
|
742
|
+
}
|
|
743
|
+
const sorted = [...flat].sort((a, b) => a - b);
|
|
744
|
+
const mid = Math.floor(sorted.length / 2);
|
|
745
|
+
return sorted.length % 2 !== 0
|
|
746
|
+
? sorted[mid]
|
|
747
|
+
: (sorted[mid - 1] + sorted[mid]) / 2;
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* **Minimum Value**
|
|
751
|
+
*
|
|
752
|
+
* Returns the minimum value from numbers or number arrays.
|
|
753
|
+
*
|
|
754
|
+
* @param numbers - Variadic numbers or number arrays.
|
|
755
|
+
* @returns The smallest number, or `Infinity` if no numbers provided.
|
|
756
|
+
*
|
|
757
|
+
* @example
|
|
758
|
+
* ```ts
|
|
759
|
+
* num.min(10, 5, 20); // 5
|
|
760
|
+
* num.min([12, 4, 89]); // 4
|
|
761
|
+
* ```
|
|
762
|
+
*/
|
|
763
|
+
min(...numbers) {
|
|
764
|
+
const flat = this.flattenNumbers(numbers);
|
|
765
|
+
if (flat.length === 0) {
|
|
766
|
+
return Infinity;
|
|
767
|
+
}
|
|
768
|
+
return Math.min(...flat);
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* **Maximum Value**
|
|
772
|
+
*
|
|
773
|
+
* Returns the maximum value from numbers or number arrays.
|
|
774
|
+
*
|
|
775
|
+
* @param numbers - Variadic numbers or number arrays.
|
|
776
|
+
* @returns The largest number, or `-Infinity` if no numbers provided.
|
|
777
|
+
*
|
|
778
|
+
* @example
|
|
779
|
+
* ```ts
|
|
780
|
+
* num.max(10, 5, 20); // 20
|
|
781
|
+
* num.max([12, 4, 89]); // 89
|
|
782
|
+
* ```
|
|
783
|
+
*/
|
|
784
|
+
max(...numbers) {
|
|
785
|
+
const flat = this.flattenNumbers(numbers);
|
|
786
|
+
if (flat.length === 0) {
|
|
787
|
+
return -Infinity;
|
|
788
|
+
}
|
|
789
|
+
return Math.max(...flat);
|
|
790
|
+
}
|
|
791
|
+
// ─────────────────────────────────────────────
|
|
792
|
+
// Pagination & Integer Division
|
|
793
|
+
// ─────────────────────────────────────────────
|
|
794
|
+
/**
|
|
795
|
+
* **Calculate Pagination**
|
|
796
|
+
*
|
|
797
|
+
* Computes all essential pagination parameters (`totalPages`, `offset`, `limit`, `startIndex`, `endIndex`, `hasNext`, `hasPrev`)
|
|
798
|
+
* for database queries and user interfaces.
|
|
799
|
+
*
|
|
800
|
+
* @param totalItems - Total number of items across the entire dataset.
|
|
801
|
+
* @param pageSize - Items per page (minimum 1).
|
|
802
|
+
* @param currentPage - Requested 1-based page number (default: `1`).
|
|
803
|
+
* @returns A structured {@link PaginationResult} object.
|
|
804
|
+
*
|
|
805
|
+
* @example
|
|
806
|
+
* ```ts
|
|
807
|
+
* const pageInfo = num.paginate(154, 20, 2);
|
|
808
|
+
* // {
|
|
809
|
+
* // currentPage: 2,
|
|
810
|
+
* // totalPages: 8,
|
|
811
|
+
* // pageSize: 20,
|
|
812
|
+
* // totalItems: 154,
|
|
813
|
+
* // offset: 20,
|
|
814
|
+
* // limit: 20,
|
|
815
|
+
* // startIndex: 20,
|
|
816
|
+
* // endIndex: 39,
|
|
817
|
+
* // hasNext: true,
|
|
818
|
+
* // hasPrev: true
|
|
819
|
+
* // }
|
|
820
|
+
* ```
|
|
821
|
+
*/
|
|
822
|
+
paginate(totalItems, pageSize, currentPage = 1) {
|
|
823
|
+
const safeTotal = Math.max(0, totalItems);
|
|
824
|
+
const safePageSize = Math.max(1, pageSize);
|
|
825
|
+
const totalPages = Math.max(1, Math.ceil(safeTotal / safePageSize));
|
|
826
|
+
const safeCurrentPage = this.clamp(currentPage, 1, totalPages);
|
|
827
|
+
const offset = (safeCurrentPage - 1) * safePageSize;
|
|
828
|
+
const limit = safePageSize;
|
|
829
|
+
const startIndex = safeTotal === 0 ? 0 : offset;
|
|
830
|
+
const endIndex = safeTotal === 0
|
|
831
|
+
? 0
|
|
832
|
+
: Math.min(offset + safePageSize - 1, safeTotal - 1);
|
|
833
|
+
return {
|
|
834
|
+
currentPage: safeCurrentPage,
|
|
835
|
+
totalPages,
|
|
836
|
+
pageSize: safePageSize,
|
|
837
|
+
totalItems: safeTotal,
|
|
838
|
+
offset,
|
|
839
|
+
limit,
|
|
840
|
+
startIndex,
|
|
841
|
+
endIndex,
|
|
842
|
+
hasNext: safeCurrentPage < totalPages,
|
|
843
|
+
hasPrev: safeCurrentPage > 1,
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* **Euclidean Division (DivMod)**
|
|
848
|
+
*
|
|
849
|
+
* Simultaneously calculates the integer quotient and remainder of two numbers: `[quotient, remainder]`.
|
|
850
|
+
* Extremely convenient for converting time units (seconds to minutes/seconds), grid positioning, and pagination.
|
|
851
|
+
*
|
|
852
|
+
* @param numerator - The dividend.
|
|
853
|
+
* @param denominator - The divisor.
|
|
854
|
+
* @returns A tuple `[quotient, remainder]`.
|
|
855
|
+
*
|
|
856
|
+
* @example
|
|
857
|
+
* ```ts
|
|
858
|
+
* const [minutes, seconds] = num.divmod(135, 60); // [2, 15] (2 min 15 sec)
|
|
859
|
+
* const [hours, remainingMin] = num.divmod(150, 60); // [2, 30] (2 h 30 min)
|
|
860
|
+
* ```
|
|
861
|
+
*/
|
|
862
|
+
divmod(numerator, denominator) {
|
|
863
|
+
if (denominator === 0) {
|
|
864
|
+
return [NaN, NaN];
|
|
865
|
+
}
|
|
866
|
+
const quotient = Math.trunc(numerator / denominator);
|
|
867
|
+
const remainder = numerator % denominator;
|
|
868
|
+
return [quotient, remainder];
|
|
869
|
+
}
|
|
870
|
+
// ─────────────────────────────────────────────
|
|
871
|
+
// Secure Randomness
|
|
872
|
+
// ─────────────────────────────────────────────
|
|
31
873
|
/**
|
|
32
874
|
* **Get a Random Integer**
|
|
33
875
|
*
|
|
34
|
-
*
|
|
876
|
+
* Generates a cryptographically secure random integer.
|
|
877
|
+
* Powered by `xypriss-security` Random core.
|
|
878
|
+
*
|
|
879
|
+
* - If one argument is provided: `[0, max)`
|
|
880
|
+
* - If two arguments are provided: `[min, max)`
|
|
35
881
|
*
|
|
36
|
-
* @param
|
|
37
|
-
* @param max
|
|
38
|
-
* @returns A random integer.
|
|
882
|
+
* @param minOrMax - Minimum value (inclusive) or maximum if second argument is omitted.
|
|
883
|
+
* @param max - Maximum value (exclusive).
|
|
884
|
+
* @returns A secure random integer.
|
|
885
|
+
*
|
|
886
|
+
* @example
|
|
887
|
+
* ```ts
|
|
888
|
+
* num.randomInt(10); // Secure integer in [0, 10)
|
|
889
|
+
* num.randomInt(10, 50); // Secure integer in [10, 50)
|
|
890
|
+
* ```
|
|
39
891
|
*/
|
|
40
|
-
randomInt(
|
|
41
|
-
return
|
|
892
|
+
randomInt(...I) {
|
|
893
|
+
return Random.Int(...I);
|
|
42
894
|
}
|
|
895
|
+
/**
|
|
896
|
+
* **Get a Random Float**
|
|
897
|
+
*
|
|
898
|
+
* Generates a cryptographically secure random floating-point number between `min` and `max`.
|
|
899
|
+
*
|
|
900
|
+
* @param min - Lower bound (default: `0`).
|
|
901
|
+
* @param max - Upper bound (default: `1`).
|
|
902
|
+
* @param decimals - Decimal places (default: `4`).
|
|
903
|
+
* @returns A secure random float.
|
|
904
|
+
*
|
|
905
|
+
* @example
|
|
906
|
+
* ```ts
|
|
907
|
+
* num.randomFloat(); // e.g. 0.4821
|
|
908
|
+
* num.randomFloat(1.5, 9.5, 2); // e.g. 6.34
|
|
909
|
+
* ```
|
|
910
|
+
*/
|
|
911
|
+
randomFloat(min = 0, max = 1, decimals = 4) {
|
|
912
|
+
let low = min;
|
|
913
|
+
let high = max;
|
|
914
|
+
if (low > high) {
|
|
915
|
+
[low, high] = [high, low];
|
|
916
|
+
}
|
|
917
|
+
const bytes = Random.Bytes(4);
|
|
918
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, 4);
|
|
919
|
+
const uint32 = view.getUint32(0, true);
|
|
920
|
+
const norm = uint32 / 0xffffffff;
|
|
921
|
+
const val = low + norm * (high - low);
|
|
922
|
+
return this.round(val, decimals);
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* **Random Item Selection**
|
|
926
|
+
*
|
|
927
|
+
* Randomly picks an element from an array using cryptographically secure random numbers.
|
|
928
|
+
*
|
|
929
|
+
* @param items - The candidate array.
|
|
930
|
+
* @returns A selected item from the array.
|
|
931
|
+
*
|
|
932
|
+
* @example
|
|
933
|
+
* ```ts
|
|
934
|
+
* num.randomChoice(["apple", "banana", "cherry"]); // e.g. "banana"
|
|
935
|
+
* ```
|
|
936
|
+
*/
|
|
937
|
+
randomChoice(items) {
|
|
938
|
+
return Random.pick(items);
|
|
939
|
+
}
|
|
940
|
+
// ─────────────────────────────────────────────
|
|
941
|
+
// Formatting & Localization
|
|
942
|
+
// ─────────────────────────────────────────────
|
|
43
943
|
/**
|
|
44
944
|
* **Format Number**
|
|
45
945
|
*
|
|
46
|
-
* Locale-aware number formatting.
|
|
946
|
+
* Locale-aware number formatting powered by `Intl.NumberFormat`.
|
|
47
947
|
*
|
|
48
948
|
* @param value - The number to format.
|
|
49
|
-
* @param locale - Optional locale (default: `en-US`).
|
|
50
|
-
* @param options - Intl.NumberFormatOptions
|
|
949
|
+
* @param locale - Optional BCP 47 locale (default: `en-US`).
|
|
950
|
+
* @param options - `Intl.NumberFormatOptions`.
|
|
51
951
|
* @returns A localized string.
|
|
952
|
+
*
|
|
953
|
+
* @example
|
|
954
|
+
* ```ts
|
|
955
|
+
* num.formatNumber(1250000); // "1,250,000"
|
|
956
|
+
* num.formatNumber(1250000, "fr-FR"); // "1 250 000"
|
|
957
|
+
* ```
|
|
52
958
|
*/
|
|
53
959
|
formatNumber(value, locale = "en-US", options) {
|
|
54
960
|
return new Intl.NumberFormat(locale, options).format(value);
|
|
55
961
|
}
|
|
56
962
|
/**
|
|
57
|
-
* **
|
|
963
|
+
* **Compact Number Formatting**
|
|
58
964
|
*
|
|
59
|
-
*
|
|
965
|
+
* Formats large numbers compactly for dashboards, views, and metrics (e.g., `1.5K`, `2.3M`, `4.8B`).
|
|
60
966
|
*
|
|
61
|
-
* @param
|
|
62
|
-
* @param
|
|
63
|
-
* @
|
|
967
|
+
* @param value - The number to format.
|
|
968
|
+
* @param locale - Locale tag (default: `en-US`).
|
|
969
|
+
* @param decimals - Decimal digits (default: `1`).
|
|
970
|
+
* @returns A compact string.
|
|
971
|
+
*
|
|
972
|
+
* @example
|
|
973
|
+
* ```ts
|
|
974
|
+
* num.formatCompact(1500); // "1.5K"
|
|
975
|
+
* num.formatCompact(2500000); // "2.5M"
|
|
976
|
+
* num.formatCompact(1200000000); // "1.2B"
|
|
977
|
+
* ```
|
|
64
978
|
*/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
979
|
+
formatCompact(value, locale = "en-US", decimals = 1) {
|
|
980
|
+
return new Intl.NumberFormat(locale, {
|
|
981
|
+
notation: "compact",
|
|
982
|
+
compactDisplay: "short",
|
|
983
|
+
maximumFractionDigits: decimals,
|
|
984
|
+
}).format(value);
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* **Format Currency**
|
|
988
|
+
*
|
|
989
|
+
* Formats a numeric value into a localized currency string.
|
|
990
|
+
*
|
|
991
|
+
* @param value - The amount to format.
|
|
992
|
+
* @param currency - ISO 4217 currency code (default: `"USD"`).
|
|
993
|
+
* @param locale - Locale tag (default: `"en-US"`).
|
|
994
|
+
* @param options - Additional `Intl.NumberFormatOptions`.
|
|
995
|
+
* @returns Formatted currency string.
|
|
996
|
+
*
|
|
997
|
+
* @example
|
|
998
|
+
* ```ts
|
|
999
|
+
* num.formatCurrency(49.99, "USD"); // "$49.99"
|
|
1000
|
+
* num.formatCurrency(49.99, "EUR", "fr-FR"); // "49,99 €"
|
|
1001
|
+
* ```
|
|
1002
|
+
*/
|
|
1003
|
+
formatCurrency(value, currency = "USD", locale = "en-US", options) {
|
|
1004
|
+
return new Intl.NumberFormat(locale, {
|
|
1005
|
+
style: "currency",
|
|
1006
|
+
currency,
|
|
1007
|
+
...options,
|
|
1008
|
+
}).format(value);
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* **Format Percentage String**
|
|
1012
|
+
*
|
|
1013
|
+
* Formats a numeric value into a localized percentage string with the `%` symbol.
|
|
1014
|
+
*
|
|
1015
|
+
* @param value - The percentage value (e.g. `15.5` or `0.155` if `isRatio: true`).
|
|
1016
|
+
* @param decimals - Decimal places (default: `1`).
|
|
1017
|
+
* @param locale - Locale tag (default: `"en-US"`).
|
|
1018
|
+
* @param isRatio - If `true`, multiplies value by 100 first (default: `false`).
|
|
1019
|
+
* @returns Formatted percentage string.
|
|
1020
|
+
*
|
|
1021
|
+
* @example
|
|
1022
|
+
* ```ts
|
|
1023
|
+
* num.formatPercent(15.5); // "15.5%"
|
|
1024
|
+
* num.formatPercent(0.155, 1, "en-US", true); // "15.5%"
|
|
1025
|
+
* ```
|
|
1026
|
+
*/
|
|
1027
|
+
formatPercent(value, decimals = 1, locale = "en-US", isRatio = false) {
|
|
1028
|
+
const percentValue = isRatio ? value * 100 : value;
|
|
1029
|
+
const formatted = this.formatNumber(this.round(percentValue, decimals), locale, {
|
|
1030
|
+
minimumFractionDigits: decimals,
|
|
1031
|
+
maximumFractionDigits: decimals,
|
|
1032
|
+
});
|
|
1033
|
+
return `${formatted}%`;
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* **Ordinal Number Suffix**
|
|
1037
|
+
*
|
|
1038
|
+
* Appends the appropriate ordinal suffix to a number (e.g. `1st`, `2nd`, `3rd`, `4th` in English, or `1er`, `2e` in French).
|
|
1039
|
+
*
|
|
1040
|
+
* @param num - The integer number.
|
|
1041
|
+
* @param locale - Target locale (default: `"en-US"`).
|
|
1042
|
+
* @returns The number with its ordinal suffix.
|
|
1043
|
+
*
|
|
1044
|
+
* @example
|
|
1045
|
+
* ```ts
|
|
1046
|
+
* num.ordinal(1); // "1st"
|
|
1047
|
+
* num.ordinal(2); // "2nd"
|
|
1048
|
+
* num.ordinal(23); // "23rd"
|
|
1049
|
+
* num.ordinal(1, "fr-FR"); // "1er"
|
|
1050
|
+
* num.ordinal(2, "fr-FR"); // "2e"
|
|
1051
|
+
* ```
|
|
1052
|
+
*/
|
|
1053
|
+
ordinal(num, locale = "en-US") {
|
|
1054
|
+
const intVal = Math.trunc(num);
|
|
1055
|
+
if (locale.startsWith("fr")) {
|
|
1056
|
+
return intVal === 1 ? "1er" : `${intVal}e`;
|
|
1057
|
+
}
|
|
1058
|
+
const abs = Math.abs(intVal);
|
|
1059
|
+
const mod100 = abs % 100;
|
|
1060
|
+
if (mod100 >= 11 && mod100 <= 13) {
|
|
1061
|
+
return `${intVal}th`;
|
|
1062
|
+
}
|
|
1063
|
+
switch (abs % 10) {
|
|
1064
|
+
case 1:
|
|
1065
|
+
return `${intVal}st`;
|
|
1066
|
+
case 2:
|
|
1067
|
+
return `${intVal}nd`;
|
|
1068
|
+
case 3:
|
|
1069
|
+
return `${intVal}rd`;
|
|
1070
|
+
default:
|
|
1071
|
+
return `${intVal}th`;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
// ─────────────────────────────────────────────
|
|
1075
|
+
// Predicates & Checks
|
|
1076
|
+
// ─────────────────────────────────────────────
|
|
1077
|
+
/**
|
|
1078
|
+
* **Check Even**
|
|
1079
|
+
*
|
|
1080
|
+
* Returns `true` if the number is an even integer.
|
|
1081
|
+
*
|
|
1082
|
+
* @param value - The number to test.
|
|
1083
|
+
* @returns `true` if even.
|
|
1084
|
+
*
|
|
1085
|
+
* @example
|
|
1086
|
+
* ```ts
|
|
1087
|
+
* num.isEven(4); // true
|
|
1088
|
+
* num.isEven(3); // false
|
|
1089
|
+
* ```
|
|
1090
|
+
*/
|
|
1091
|
+
isEven(value) {
|
|
1092
|
+
return value % 2 === 0;
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* **Check Odd**
|
|
1096
|
+
*
|
|
1097
|
+
* Returns `true` if the number is an odd integer.
|
|
1098
|
+
*
|
|
1099
|
+
* @param value - The number to test.
|
|
1100
|
+
* @returns `true` if odd.
|
|
1101
|
+
*
|
|
1102
|
+
* @example
|
|
1103
|
+
* ```ts
|
|
1104
|
+
* num.isOdd(3); // true
|
|
1105
|
+
* num.isOdd(4); // false
|
|
1106
|
+
* ```
|
|
1107
|
+
*/
|
|
1108
|
+
isOdd(value) {
|
|
1109
|
+
return Math.abs(value % 2) === 1;
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* **Check Integer**
|
|
1113
|
+
*
|
|
1114
|
+
* Returns `true` if the value is a safe, finite integer.
|
|
1115
|
+
*
|
|
1116
|
+
* @param value - The number to test.
|
|
1117
|
+
* @returns `true` if an integer.
|
|
1118
|
+
*
|
|
1119
|
+
* @example
|
|
1120
|
+
* ```ts
|
|
1121
|
+
* num.isInteger(42); // true
|
|
1122
|
+
* num.isInteger(42.5); // false
|
|
1123
|
+
* ```
|
|
1124
|
+
*/
|
|
1125
|
+
isInteger(value) {
|
|
1126
|
+
return Number.isInteger(value);
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* **Check Numeric**
|
|
1130
|
+
*
|
|
1131
|
+
* Checks if a value is a valid numeric value or a numeric string representing a finite number.
|
|
1132
|
+
*
|
|
1133
|
+
* @param value - Any value to test.
|
|
1134
|
+
* @returns Type guard confirming if the value is numeric.
|
|
1135
|
+
*
|
|
1136
|
+
* @example
|
|
1137
|
+
* ```ts
|
|
1138
|
+
* num.isNumeric(42); // true
|
|
1139
|
+
* num.isNumeric("42.5"); // true
|
|
1140
|
+
* num.isNumeric("abc"); // false
|
|
1141
|
+
* num.isNumeric(null); // false
|
|
1142
|
+
* ```
|
|
1143
|
+
*/
|
|
1144
|
+
isNumeric(value) {
|
|
1145
|
+
if (typeof value === "number") {
|
|
1146
|
+
return !isNaN(value) && isFinite(value);
|
|
1147
|
+
}
|
|
1148
|
+
if (typeof value !== "string") {
|
|
1149
|
+
return false;
|
|
1150
|
+
}
|
|
1151
|
+
return (value.trim() !== "" &&
|
|
1152
|
+
!isNaN(Number(value)) &&
|
|
1153
|
+
isFinite(Number(value)));
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* **Sign of a Number**
|
|
1157
|
+
*
|
|
1158
|
+
* Returns `-1` for negative numbers, `1` for positive numbers, and `0` for zero.
|
|
1159
|
+
*
|
|
1160
|
+
* @param value - The number.
|
|
1161
|
+
* @returns `-1 | 0 | 1`.
|
|
1162
|
+
*
|
|
1163
|
+
* @example
|
|
1164
|
+
* ```ts
|
|
1165
|
+
* num.sign(-42); // -1
|
|
1166
|
+
* num.sign(42); // 1
|
|
1167
|
+
* num.sign(0); // 0
|
|
1168
|
+
* ```
|
|
1169
|
+
*/
|
|
1170
|
+
sign(value) {
|
|
1171
|
+
if (value > 0)
|
|
1172
|
+
return 1;
|
|
1173
|
+
if (value < 0)
|
|
1174
|
+
return -1;
|
|
1175
|
+
return 0;
|
|
73
1176
|
}
|
|
74
1177
|
}
|
|
75
1178
|
|