@c9up/atom 0.1.5 → 0.1.8

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 (45) hide show
  1. package/README.md +60 -10
  2. package/dist/Decimal.d.ts +13 -1
  3. package/dist/Decimal.d.ts.map +1 -1
  4. package/dist/Decimal.js +312 -39
  5. package/dist/Decimal.js.map +1 -1
  6. package/dist/Money.d.ts +33 -0
  7. package/dist/Money.d.ts.map +1 -0
  8. package/dist/Money.js +128 -0
  9. package/dist/Money.js.map +1 -0
  10. package/dist/atlas.d.ts +22 -5
  11. package/dist/atlas.d.ts.map +1 -1
  12. package/dist/atlas.js +41 -0
  13. package/dist/atlas.js.map +1 -1
  14. package/dist/context.d.ts +15 -0
  15. package/dist/context.d.ts.map +1 -0
  16. package/dist/context.js +46 -0
  17. package/dist/context.js.map +1 -0
  18. package/dist/index.d.ts +10 -2
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +12 -5
  21. package/dist/index.js.map +1 -1
  22. package/dist/math.d.ts.map +1 -1
  23. package/dist/math.js +53 -3
  24. package/dist/math.js.map +1 -1
  25. package/dist/native.d.ts +3 -2
  26. package/dist/native.d.ts.map +1 -1
  27. package/dist/native.js +8 -2
  28. package/dist/native.js.map +1 -1
  29. package/index.darwin-arm64.node +0 -0
  30. package/index.darwin-x64.node +0 -0
  31. package/index.linux-arm64-gnu.node +0 -0
  32. package/index.linux-x64-gnu.node +0 -0
  33. package/index.win32-x64-msvc.node +0 -0
  34. package/package.json +3 -3
  35. package/scripts/bench.mjs +26 -0
  36. package/scripts/verify-napi.mjs +10 -1
  37. package/scripts/verify-wasm.mjs +56 -0
  38. package/src/Decimal.ts +403 -47
  39. package/src/Money.ts +179 -0
  40. package/src/atlas.ts +78 -2
  41. package/src/context.ts +67 -0
  42. package/src/index.ts +20 -5
  43. package/src/math.ts +56 -3
  44. package/src/native.ts +9 -2
  45. package/wasm/atom_engine_wasm.d.ts +0 -19
package/README.md CHANGED
@@ -1,29 +1,79 @@
1
1
  # @c9up/atom
2
2
 
3
- > Exact decimal arithmetic + statistics for the Ream ecosystem (TypeScript + Rust N-API). No floating-point drift.
3
+ Exact decimal arithmetic, statistics, and currency-safe money values for the
4
+ Ream ecosystem. Rust NAPI/WASM is used when available; the TypeScript BigInt
5
+ engine is the fallback.
4
6
 
5
- Part of **[Ream](https://github.com/C9up/ream)** — a Rust-powered, AdonisJS-compatible Node.js framework. Independent, publishable package.
6
-
7
- ## Installation
7
+ ## Install
8
8
 
9
9
  ```bash
10
10
  pnpm add @c9up/atom
11
11
  ```
12
12
 
13
- ## Usage
13
+ ## Decimal
14
+
15
+ ```ts
16
+ import { Decimal, decimal, sum } from '@c9up/atom'
17
+
18
+ decimal('0.1').plus('0.2').toString() // "0.3"
19
+ sum(['1.1', '2.2', '3.3']).toString() // "6.6"
20
+
21
+ Decimal.safeParse('12.34') // { success: true, value: Decimal }
22
+ Decimal.tryParse('bad') // null
23
+ ```
24
+
25
+ Prefer string or bigint inputs for exact user/business data. Unsafe JS integer
26
+ numbers are rejected.
27
+
28
+ ## Money
29
+
30
+ ```ts
31
+ import { money, Money } from '@c9up/atom'
32
+
33
+ const total = money('10.00', 'USD').allocate([1, 1, 1])
34
+ total.map((part) => part.toString()) // ["3.34 USD", "3.33 USD", "3.33 USD"]
35
+
36
+ Money.fromMinorUnits(1999n, 'EUR').format({ locale: 'fr-FR' })
37
+ ```
38
+
39
+ `Money` keeps currency and scale together, and rejects operations across
40
+ different currencies.
41
+
42
+ ## Context
43
+
44
+ ```ts
45
+ import { configureAtomContext, decimal, withAtomContext } from '@c9up/atom'
46
+
47
+ configureAtomContext({ precision: 8, roundMode: 'trunc' })
48
+ withAtomContext({ precision: 2 }, () => decimal('1').div('8').toString())
49
+ ```
50
+
51
+ ## Atlas
14
52
 
15
53
  ```ts
16
- import { decimal, sum, avg, median, stddev } from '@c9up/atom'
54
+ import { Column } from '@c9up/atlas'
55
+ import { decimalColumn } from '@c9up/atom/atlas'
56
+
57
+ class Invoice {
58
+ @Column(decimalColumn({ scale: 2, nullable: false }))
59
+ total!: Decimal
60
+ }
61
+ ```
62
+
63
+ ## Scripts
17
64
 
18
- const price = decimal('19.99') // exact decimal, no float drift
19
- sum(['1.1', '2.2', '3.3']) // exact aggregate
20
- avg(values); median(values); stddev(values)
65
+ ```bash
66
+ pnpm test
67
+ pnpm test:napi
68
+ pnpm test:coverage
69
+ pnpm bench
70
+ pnpm build:wasm && node scripts/verify-wasm.mjs
21
71
  ```
22
72
 
23
73
  ## Entry points
24
74
 
25
75
  - `@c9up/atom` — main API
26
- - `@c9up/atom/atlas` — Atlas integration adapter
76
+ - `@c9up/atom/atlas` — Atlas column helpers
27
77
 
28
78
  ## License
29
79
 
package/dist/Decimal.d.ts CHANGED
@@ -33,10 +33,21 @@ export interface ToMinorUnitsOptions {
33
33
  exact?: boolean;
34
34
  mode?: RoundMode;
35
35
  }
36
+ export type DecimalSafeParseResult = {
37
+ success: true;
38
+ value: Decimal;
39
+ } | {
40
+ success: false;
41
+ error: Error;
42
+ };
36
43
  export declare class Decimal {
37
44
  #private;
38
- constructor(value: DecimalInput);
45
+ constructor(value: DecimalInput, scaleHint?: number);
39
46
  static from(value: DecimalInput): Decimal;
47
+ static parse(value: DecimalInput): Decimal;
48
+ static tryParse(value: unknown): Decimal | null;
49
+ static safeParse(value: unknown): DecimalSafeParseResult;
50
+ static isDecimal(value: unknown): value is Decimal;
40
51
  static zero(): Decimal;
41
52
  static one(): Decimal;
42
53
  static fromMinorUnits(value: string | number | bigint, scale: number): Decimal;
@@ -132,6 +143,7 @@ export declare class Decimal {
132
143
  * Provided for interop with APIs that expect a primitive number.
133
144
  */
134
145
  toNumber(): number;
146
+ private sqrtTruncInt;
135
147
  /**
136
148
  * Format the value as a localized string via `Intl.NumberFormat`.
137
149
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Decimal.d.ts","sourceRoot":"","sources":["../src/Decimal.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Decimal.d.ts","sourceRoot":"","sources":["../src/Decimal.ts"],"names":[],"mappings":"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"}
package/dist/Decimal.js CHANGED
@@ -1,13 +1,49 @@
1
- import { formatDecimal, parseDecimal, pow10BigInt } from "./math.js";
2
- import { nativeAtom } from "./native.js";
1
+ import { defaultPrecision, defaultQuantizeMode, defaultRoundMode, } from "./context.js";
2
+ import { addTs, cmpTs, divTs, formatDecimal, modTs, mulTs, parseDecimal, pow10BigInt, powTs, sqrtTs, subTs, } from "./math.js";
3
+ import { tryNativeAtom } from "./native.js";
4
+ const MAX_SCALE = 10_000;
5
+ const MAX_EXPONENT_ABS = 100_000;
3
6
  export class Decimal {
4
7
  #value;
5
- constructor(value) {
8
+ #scaleHint;
9
+ constructor(value, scaleHint) {
10
+ if (value instanceof Decimal) {
11
+ this.#value = value.#value;
12
+ this.#scaleHint = scaleHint ?? value.#scaleHint;
13
+ assertScale(this.#scaleHint);
14
+ return;
15
+ }
6
16
  this.#value = normalizeInput(value);
17
+ this.#scaleHint = scaleHint ?? inferInputScale(value, this.#value);
18
+ assertScale(this.#scaleHint);
7
19
  }
8
20
  static from(value) {
9
21
  return new Decimal(value);
10
22
  }
23
+ static parse(value) {
24
+ return new Decimal(value);
25
+ }
26
+ static tryParse(value) {
27
+ const parsed = Decimal.safeParse(value);
28
+ return parsed.success ? parsed.value : null;
29
+ }
30
+ static safeParse(value) {
31
+ try {
32
+ if (!isDecimalInput(value)) {
33
+ throw new Error(`Invalid decimal input type: ${typeof value}`);
34
+ }
35
+ return { success: true, value: new Decimal(value) };
36
+ }
37
+ catch (error) {
38
+ return {
39
+ success: false,
40
+ error: error instanceof Error ? error : new Error(String(error)),
41
+ };
42
+ }
43
+ }
44
+ static isDecimal(value) {
45
+ return value instanceof Decimal;
46
+ }
11
47
  static zero() {
12
48
  return new Decimal("0");
13
49
  }
@@ -27,56 +63,63 @@ export class Decimal {
27
63
  }
28
64
  plus(other) {
29
65
  const b = normalizeInput(other);
30
- const result = nativeAtom().add(this.#value, b);
66
+ const native = tryNativeAtom();
67
+ const result = native ? native.add(this.#value, b) : addTs(this.#value, b);
31
68
  return new Decimal(result);
32
69
  }
33
70
  minus(other) {
34
71
  const b = normalizeInput(other);
35
- const result = nativeAtom().sub(this.#value, b);
72
+ const native = tryNativeAtom();
73
+ const result = native ? native.sub(this.#value, b) : subTs(this.#value, b);
36
74
  return new Decimal(result);
37
75
  }
38
76
  times(other) {
39
77
  const b = normalizeInput(other);
40
- const result = nativeAtom().mul(this.#value, b);
78
+ const native = tryNativeAtom();
79
+ const result = native ? native.mul(this.#value, b) : mulTs(this.#value, b);
41
80
  return new Decimal(result);
42
81
  }
43
82
  div(other, options = {}) {
44
83
  const b = normalizeInput(other);
45
- const precision = options.precision ?? 18;
84
+ const precision = options.precision ?? defaultPrecision();
46
85
  assertScale(precision);
47
- const result = nativeAtom().div(this.#value, b, precision);
86
+ const native = tryNativeAtom();
87
+ const result = native
88
+ ? native.div(this.#value, b, precision)
89
+ : divTs(this.#value, b, precision);
48
90
  return new Decimal(result);
49
91
  }
50
92
  mod(other) {
51
93
  const b = normalizeInput(other);
52
- const result = nativeAtom().rem(this.#value, b);
94
+ const native = tryNativeAtom();
95
+ const result = native ? native.rem(this.#value, b) : modTs(this.#value, b);
53
96
  return new Decimal(result);
54
97
  }
55
98
  pow(exp, options = {}) {
56
- if (!Number.isInteger(exp)) {
57
- throw new Error(`Invalid exponent: ${exp}`);
58
- }
59
- const precision = options.precision ?? 18;
99
+ assertExponent(exp);
100
+ const precision = options.precision ?? defaultPrecision();
60
101
  assertScale(precision);
61
- const result = nativeAtom().pow(this.#value, exp, precision);
102
+ const native = tryNativeAtom();
103
+ const result = native
104
+ ? native.pow(this.#value, exp, precision)
105
+ : powTs(this.#value, exp, precision);
62
106
  return new Decimal(result);
63
107
  }
64
108
  sqrt(options = {}) {
65
- const precision = options.precision ?? 18;
66
- const mode = options.mode ?? "trunc";
109
+ const precision = options.precision ?? defaultPrecision();
110
+ const mode = options.mode ?? defaultRoundMode();
67
111
  assertScale(precision);
68
112
  if (mode === "trunc") {
69
- const result = nativeAtom().sqrt(this.#value, precision);
70
- return new Decimal(result);
113
+ return fromIntScale(this.sqrtTruncInt(precision), precision);
71
114
  }
72
- const withExtra = nativeAtom().sqrt(this.#value, precision + 1);
73
- const parsed = parseDecimal(withExtra);
74
- const rounded = roundIntScale(parsed.int, precision + 1, precision, mode);
115
+ const truncated = this.sqrtTruncInt(precision);
116
+ const rounded = roundSqrtInt(this.#value, truncated, precision, mode);
75
117
  return fromIntScale(rounded, precision);
76
118
  }
77
119
  cmp(other) {
78
120
  const b = normalizeInput(other);
79
- const result = nativeAtom().cmp(this.#value, b);
121
+ const native = tryNativeAtom();
122
+ const result = native ? native.cmp(this.#value, b) : cmpTs(this.#value, b);
80
123
  if (result < 0)
81
124
  return -1;
82
125
  if (result > 0)
@@ -172,15 +215,19 @@ export class Decimal {
172
215
  * new Decimal('1.025').quantize('0.05') // → Decimal('1.05')
173
216
  */
174
217
  quantize(step, options = {}) {
175
- const { mode = "half-up" } = options;
218
+ const { mode = defaultQuantizeMode() } = options;
176
219
  const stepValue = new Decimal(step);
177
220
  if (!stepValue.gt(0)) {
178
221
  throw new Error("Quantize step must be greater than zero");
179
222
  }
180
- const thisScale = this.toParts().scale;
181
- const stepScale = stepValue.toParts().scale;
182
- const precision = options.precision ?? Math.max(18, thisScale + stepScale + 6);
183
- const units = this.div(stepValue, { precision }).round(0, mode);
223
+ if (options.precision !== undefined) {
224
+ assertScale(options.precision);
225
+ }
226
+ const thisParts = parseDecimal(this.#value);
227
+ const stepParts = parseDecimal(stepValue.#value);
228
+ const numerator = thisParts.int * pow10BigInt(stepParts.scale);
229
+ const denominator = stepParts.int * pow10BigInt(thisParts.scale);
230
+ const units = fromIntScale(roundRationalToInt(numerator, denominator, mode), 0);
184
231
  return units.times(stepValue);
185
232
  }
186
233
  toScale(scale = 0, mode = "trunc") {
@@ -282,9 +329,9 @@ export class Decimal {
282
329
  if (ratioTotal <= 0n) {
283
330
  throw new Error("Allocate requires at least one positive ratio");
284
331
  }
285
- const parsed = parseDecimal(this.#value);
286
- const sign = parsed.int < 0n ? -1n : 1n;
287
- const total = parsed.int < 0n ? -parsed.int : parsed.int;
332
+ const parsed = this.toParts();
333
+ const sign = parsed.value < 0n ? -1n : 1n;
334
+ const total = parsed.value < 0n ? -parsed.value : parsed.value;
288
335
  const baseShares = [];
289
336
  const remainders = [];
290
337
  let consumed = 0n;
@@ -317,7 +364,13 @@ export class Decimal {
317
364
  }
318
365
  toParts() {
319
366
  const parsed = parseDecimal(this.#value);
320
- return { value: parsed.int, scale: parsed.scale };
367
+ if (this.#scaleHint <= parsed.scale) {
368
+ return { value: parsed.int, scale: parsed.scale };
369
+ }
370
+ return {
371
+ value: parsed.int * pow10BigInt(this.#scaleHint - parsed.scale),
372
+ scale: this.#scaleHint,
373
+ };
321
374
  }
322
375
  toString() {
323
376
  return this.#value;
@@ -333,6 +386,14 @@ export class Decimal {
333
386
  toNumber() {
334
387
  return Number(this.#value);
335
388
  }
389
+ sqrtTruncInt(precision) {
390
+ const native = tryNativeAtom();
391
+ const result = native
392
+ ? native.sqrt(this.#value, precision)
393
+ : sqrtTs(this.#value, precision);
394
+ const parsed = parseDecimal(result);
395
+ return roundIntScale(parsed.int, parsed.scale, precision, "trunc");
396
+ }
336
397
  /**
337
398
  * Format the value as a localized string via `Intl.NumberFormat`.
338
399
  *
@@ -369,10 +430,60 @@ function normalizeInput(value) {
369
430
  if (!Number.isFinite(value)) {
370
431
  throw new Error(`Invalid decimal: ${value}`);
371
432
  }
372
- return normalizeDecimalString(String(value));
433
+ return normalizeDecimalString(decimalStringFromNumber(value));
373
434
  }
374
435
  return normalizeDecimalString(value);
375
436
  }
437
+ function isDecimalInput(value) {
438
+ return (value instanceof Decimal ||
439
+ typeof value === "string" ||
440
+ typeof value === "number" ||
441
+ typeof value === "bigint");
442
+ }
443
+ function inferInputScale(value, normalized) {
444
+ if (typeof value === "bigint")
445
+ return 0;
446
+ if (typeof value === "number") {
447
+ return parseDecimal(decimalStringFromNumber(value)).scale;
448
+ }
449
+ try {
450
+ return parseDecimal(value).scale;
451
+ }
452
+ catch {
453
+ return parseDecimal(normalized).scale;
454
+ }
455
+ }
456
+ function decimalStringFromNumber(value) {
457
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
458
+ throw new Error(`Unsafe integer decimal input: ${value}`);
459
+ }
460
+ const raw = String(value);
461
+ if (!/[eE]/.test(raw))
462
+ return raw;
463
+ const [coefficient, exponentRaw] = raw.toLowerCase().split("e");
464
+ const exponent = Number(exponentRaw);
465
+ if (!Number.isInteger(exponent)) {
466
+ throw new Error(`Invalid decimal: ${value}`);
467
+ }
468
+ const negative = coefficient.startsWith("-");
469
+ const unsigned = negative || coefficient.startsWith("+")
470
+ ? coefficient.slice(1)
471
+ : coefficient;
472
+ const [wholeRaw, fracRaw = ""] = unsigned.split(".");
473
+ const digits = `${wholeRaw}${fracRaw}`;
474
+ const decimalIndex = wholeRaw.length + exponent;
475
+ let expanded;
476
+ if (decimalIndex <= 0) {
477
+ expanded = `0.${"0".repeat(-decimalIndex)}${digits}`;
478
+ }
479
+ else if (decimalIndex >= digits.length) {
480
+ expanded = `${digits}${"0".repeat(decimalIndex - digits.length)}`;
481
+ }
482
+ else {
483
+ expanded = `${digits.slice(0, decimalIndex)}.${digits.slice(decimalIndex)}`;
484
+ }
485
+ return negative ? `-${expanded}` : expanded;
486
+ }
376
487
  function normalizeDecimalString(input) {
377
488
  const parsed = parseDecimal(input);
378
489
  return formatDecimal(parsed.int, parsed.scale);
@@ -384,6 +495,9 @@ function parseIntegerInput(value) {
384
495
  if (!Number.isInteger(value)) {
385
496
  throw new Error(`Invalid integer: ${value}`);
386
497
  }
498
+ if (!Number.isSafeInteger(value)) {
499
+ throw new Error(`Unsafe integer input: ${value}`);
500
+ }
387
501
  return BigInt(value);
388
502
  }
389
503
  const s = value.trim();
@@ -393,13 +507,20 @@ function parseIntegerInput(value) {
393
507
  return BigInt(s);
394
508
  }
395
509
  function fromIntScale(int, scale) {
396
- return new Decimal(formatDecimal(int, scale));
510
+ return new Decimal(formatDecimal(int, scale), scale);
397
511
  }
398
512
  function assertScale(scale) {
399
- if (!Number.isInteger(scale) || scale < 0) {
513
+ if (!Number.isInteger(scale) || scale < 0 || scale > MAX_SCALE) {
400
514
  throw new Error(`Invalid scale: ${scale}`);
401
515
  }
402
516
  }
517
+ function assertExponent(exp) {
518
+ if (!Number.isInteger(exp) ||
519
+ exp < -MAX_EXPONENT_ABS ||
520
+ exp > MAX_EXPONENT_ABS) {
521
+ throw new Error(`Invalid exponent: ${exp}`);
522
+ }
523
+ }
403
524
  function roundIntScale(int, sourceScale, targetScale, mode) {
404
525
  if (targetScale >= sourceScale) {
405
526
  return int * pow10BigInt(targetScale - sourceScale);
@@ -439,6 +560,73 @@ function roundIntScale(int, sourceScale, targetScale, mode) {
439
560
  throw new Error(`Unknown rounding mode: ${String(mode)}`);
440
561
  }
441
562
  }
563
+ function roundRationalToInt(numerator, denominator, mode) {
564
+ if (denominator <= 0n) {
565
+ throw new Error("Invalid rational denominator");
566
+ }
567
+ const q = numerator / denominator;
568
+ const r = numerator % denominator;
569
+ if (r === 0n)
570
+ return q;
571
+ const absR = r < 0n ? -r : r;
572
+ const sign = numerator < 0n ? -1n : 1n;
573
+ switch (mode) {
574
+ case "trunc":
575
+ return q;
576
+ case "floor":
577
+ return numerator < 0n ? q - 1n : q;
578
+ case "ceil":
579
+ return numerator > 0n ? q + 1n : q;
580
+ case "half-up":
581
+ return absR * 2n >= denominator ? q + sign : q;
582
+ case "half-even": {
583
+ const twice = absR * 2n;
584
+ if (twice < denominator)
585
+ return q;
586
+ if (twice > denominator)
587
+ return q + sign;
588
+ const isEven = (q < 0n ? -q : q) % 2n === 0n;
589
+ return isEven ? q : q + sign;
590
+ }
591
+ default:
592
+ throw new Error(`Unknown rounding mode: ${String(mode)}`);
593
+ }
594
+ }
595
+ function roundSqrtInt(value, truncated, precision, mode) {
596
+ const parsed = parseDecimal(value);
597
+ if (parsed.int < 0n) {
598
+ throw new Error("Cannot compute sqrt of a negative decimal");
599
+ }
600
+ const squareCmp = compareScaled(truncated * truncated, 2 * precision, parsed.int, parsed.scale);
601
+ if (mode === "floor")
602
+ return truncated;
603
+ if (mode === "ceil") {
604
+ return squareCmp === 0 ? truncated : truncated + 1n;
605
+ }
606
+ const thresholdCmp = compareScaled((2n * truncated + 1n) * (2n * truncated + 1n), 2 * precision, 4n * parsed.int, parsed.scale);
607
+ if (thresholdCmp < 0)
608
+ return truncated + 1n;
609
+ if (thresholdCmp > 0)
610
+ return truncated;
611
+ if (mode === "half-up")
612
+ return truncated + 1n;
613
+ return truncated % 2n === 0n ? truncated : truncated + 1n;
614
+ }
615
+ function compareScaled(leftInt, leftScale, rightInt, rightScale) {
616
+ let left = leftInt;
617
+ let right = rightInt;
618
+ if (leftScale > rightScale) {
619
+ right *= pow10BigInt(leftScale - rightScale);
620
+ }
621
+ else if (rightScale > leftScale) {
622
+ left *= pow10BigInt(rightScale - leftScale);
623
+ }
624
+ if (left < right)
625
+ return -1;
626
+ if (left > right)
627
+ return 1;
628
+ return 0;
629
+ }
442
630
  function isRosettaLike(arg) {
443
631
  return (typeof arg === "object" &&
444
632
  arg !== null &&
@@ -463,8 +651,6 @@ function normalizeViaRosetta(input, rosetta) {
463
651
  plusSign: raw.plusSign || "+",
464
652
  };
465
653
  let normalized = trimmed.replace(/\s| | /g, "");
466
- normalized = normalized.replace(new RegExp(escapeRegExp(data.group), "g"), "");
467
- normalized = normalized.replace(new RegExp(escapeRegExp(data.decimal), "g"), ".");
468
654
  if (data.minus !== "-") {
469
655
  normalized = normalized.replace(new RegExp(escapeRegExp(data.minus), "g"), "-");
470
656
  }
@@ -477,6 +663,9 @@ function normalizeViaRosetta(input, rosetta) {
477
663
  if (/^\(.*\)$/.test(normalized)) {
478
664
  normalized = `-${normalized.slice(1, -1)}`;
479
665
  }
666
+ validateLocalizedSyntax(normalized, data.group, data.decimal);
667
+ normalized = normalized.replace(new RegExp(escapeRegExp(data.group), "g"), "");
668
+ normalized = normalized.replace(new RegExp(escapeRegExp(data.decimal), "g"), ".");
480
669
  if (!/^[+-]?\d+(\.\d+)?$/.test(normalized)) {
481
670
  throw new Error(`Invalid localized decimal: ${input}`);
482
671
  }
@@ -492,9 +681,8 @@ function normalizeLocaleNumber(input, locales) {
492
681
  const group = parts.find((part) => part.type === "group")?.value ?? ",";
493
682
  const decimal = parts.find((part) => part.type === "decimal")?.value ?? ".";
494
683
  const minus = parts.find((part) => part.type === "minusSign")?.value ?? "-";
495
- let normalized = trimmed.replace(/\s|\u00A0|\u202F/g, "");
496
- normalized = normalized.replace(new RegExp(escapeRegExp(group), "g"), "");
497
- normalized = normalized.replace(new RegExp(escapeRegExp(decimal), "g"), ".");
684
+ let normalized = normalizeLocaleDigits(trimmed, locales);
685
+ normalized = normalized.replace(/\s|\u00A0|\u202F/g, "");
498
686
  if (minus !== "-") {
499
687
  normalized = normalized.replace(new RegExp(escapeRegExp(minus), "g"), "-");
500
688
  }
@@ -502,11 +690,96 @@ function normalizeLocaleNumber(input, locales) {
502
690
  if (/^\(.*\)$/.test(normalized)) {
503
691
  normalized = `-${normalized.slice(1, -1)}`;
504
692
  }
693
+ validateLocalizedSyntax(normalized, group, decimal, getLocaleGrouping(locales));
694
+ normalized = normalized.replace(new RegExp(escapeRegExp(group), "g"), "");
695
+ normalized = normalized.replace(new RegExp(escapeRegExp(decimal), "g"), ".");
505
696
  if (!/^[+-]?\d+(\.\d+)?$/.test(normalized)) {
506
697
  throw new Error(`Invalid localized decimal: ${input}`);
507
698
  }
508
699
  return normalized;
509
700
  }
701
+ function normalizeLocaleDigits(input, locales) {
702
+ const digitMap = getLocaleDigitMap(locales);
703
+ let out = "";
704
+ for (const char of input) {
705
+ out += digitMap.get(char) ?? char;
706
+ }
707
+ return out;
708
+ }
709
+ function getLocaleDigitMap(locales) {
710
+ const formatter = new Intl.NumberFormat(locales, { useGrouping: false });
711
+ const digits = formatter.format(9876543210);
712
+ const map = new Map();
713
+ let value = 9;
714
+ for (const char of digits) {
715
+ if (!map.has(char) && value >= 0) {
716
+ map.set(char, String(value));
717
+ value--;
718
+ }
719
+ }
720
+ return map;
721
+ }
722
+ function getLocaleGrouping(locales) {
723
+ const parts = new Intl.NumberFormat(locales)
724
+ .formatToParts(1234567890123)
725
+ .filter((part) => part.type === "integer" || part.type === "group");
726
+ const lengths = [];
727
+ let current = 0;
728
+ for (const part of parts) {
729
+ if (part.type === "integer") {
730
+ current += [...part.value].length;
731
+ }
732
+ else {
733
+ lengths.push(current);
734
+ current = 0;
735
+ }
736
+ }
737
+ lengths.push(current);
738
+ if (lengths.length < 2) {
739
+ return { primary: 3, secondary: 3 };
740
+ }
741
+ const primary = lengths[lengths.length - 1];
742
+ const secondary = lengths[lengths.length - 2] ?? primary;
743
+ return { primary, secondary };
744
+ }
745
+ function validateLocalizedSyntax(value, group, decimal, grouping = { primary: 3, secondary: 3 }) {
746
+ const decimalParts = value.split(decimal);
747
+ if (decimalParts.length > 2) {
748
+ throw new Error(`Invalid localized decimal: ${value}`);
749
+ }
750
+ let integer = decimalParts[0] ?? "";
751
+ const fraction = decimalParts[1];
752
+ if (integer.startsWith("+") || integer.startsWith("-")) {
753
+ integer = integer.slice(1);
754
+ }
755
+ if (!integer) {
756
+ throw new Error(`Invalid localized decimal: ${value}`);
757
+ }
758
+ if (fraction !== undefined && !/^\d+$/.test(fraction)) {
759
+ throw new Error(`Invalid localized decimal: ${value}`);
760
+ }
761
+ if (!integer.includes(group)) {
762
+ if (!/^\d+$/.test(integer)) {
763
+ throw new Error(`Invalid localized decimal: ${value}`);
764
+ }
765
+ return;
766
+ }
767
+ const groups = integer.split(group);
768
+ if (groups.some((part) => !/^\d+$/.test(part))) {
769
+ throw new Error(`Invalid localized decimal: ${value}`);
770
+ }
771
+ for (let i = groups.length - 1, distance = 0; i >= 0; i--, distance++) {
772
+ const size = distance === 0 ? grouping.primary : grouping.secondary;
773
+ if (i === 0) {
774
+ if (groups[i].length < 1 || groups[i].length > size) {
775
+ throw new Error(`Invalid localized decimal: ${value}`);
776
+ }
777
+ }
778
+ else if (groups[i].length !== size) {
779
+ throw new Error(`Invalid localized decimal: ${value}`);
780
+ }
781
+ }
782
+ }
510
783
  function escapeRegExp(value) {
511
784
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
512
785
  }