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