numeric-quantity 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/cjs/numeric-quantity.cjs.development.js +108 -3
- package/dist/cjs/numeric-quantity.cjs.development.js.map +1 -1
- package/dist/cjs/numeric-quantity.cjs.production.js +1 -1
- package/dist/cjs/numeric-quantity.cjs.production.js.map +1 -1
- package/dist/numeric-quantity.iife.umd.min.js +1 -1
- package/dist/numeric-quantity.iife.umd.min.js.map +1 -1
- package/dist/numeric-quantity.legacy-esm.js +127 -24
- package/dist/numeric-quantity.legacy-esm.js.map +1 -1
- package/dist/numeric-quantity.mjs +107 -4
- package/dist/numeric-quantity.mjs.map +1 -1
- package/dist/numeric-quantity.production.mjs +1 -1
- package/dist/numeric-quantity.production.mjs.map +1 -1
- package/dist/types/constants.d.ts +8 -0
- package/dist/types-esm/constants.d.mts +8 -0
- package/package.json +10 -10
|
@@ -1,5 +1,109 @@
|
|
|
1
1
|
//#region src/constants.ts
|
|
2
2
|
/**
|
|
3
|
+
* Unicode decimal digit block start code points.
|
|
4
|
+
* Each block contains 10 contiguous digits (0-9).
|
|
5
|
+
* This list covers all \p{Nd} (Decimal_Number) blocks through Unicode 17.0.
|
|
6
|
+
* The drift test in index.test.ts validates completeness against the JS engine.
|
|
7
|
+
*/
|
|
8
|
+
const decimalDigitBlockStarts = [
|
|
9
|
+
48,
|
|
10
|
+
1632,
|
|
11
|
+
1776,
|
|
12
|
+
1984,
|
|
13
|
+
2406,
|
|
14
|
+
2534,
|
|
15
|
+
2662,
|
|
16
|
+
2790,
|
|
17
|
+
2918,
|
|
18
|
+
3046,
|
|
19
|
+
3174,
|
|
20
|
+
3302,
|
|
21
|
+
3430,
|
|
22
|
+
3558,
|
|
23
|
+
3664,
|
|
24
|
+
3792,
|
|
25
|
+
3872,
|
|
26
|
+
4160,
|
|
27
|
+
4240,
|
|
28
|
+
6112,
|
|
29
|
+
6160,
|
|
30
|
+
6470,
|
|
31
|
+
6608,
|
|
32
|
+
6784,
|
|
33
|
+
6800,
|
|
34
|
+
6992,
|
|
35
|
+
7088,
|
|
36
|
+
7232,
|
|
37
|
+
7248,
|
|
38
|
+
42528,
|
|
39
|
+
43216,
|
|
40
|
+
43264,
|
|
41
|
+
43472,
|
|
42
|
+
43504,
|
|
43
|
+
43600,
|
|
44
|
+
44016,
|
|
45
|
+
65296,
|
|
46
|
+
66720,
|
|
47
|
+
68912,
|
|
48
|
+
68928,
|
|
49
|
+
69734,
|
|
50
|
+
69872,
|
|
51
|
+
69942,
|
|
52
|
+
70096,
|
|
53
|
+
70384,
|
|
54
|
+
70736,
|
|
55
|
+
70864,
|
|
56
|
+
71248,
|
|
57
|
+
71360,
|
|
58
|
+
71376,
|
|
59
|
+
71386,
|
|
60
|
+
71472,
|
|
61
|
+
71904,
|
|
62
|
+
72016,
|
|
63
|
+
72688,
|
|
64
|
+
72784,
|
|
65
|
+
73040,
|
|
66
|
+
73120,
|
|
67
|
+
73184,
|
|
68
|
+
73552,
|
|
69
|
+
90416,
|
|
70
|
+
92768,
|
|
71
|
+
92864,
|
|
72
|
+
93008,
|
|
73
|
+
93552,
|
|
74
|
+
118e3,
|
|
75
|
+
120782,
|
|
76
|
+
120792,
|
|
77
|
+
120802,
|
|
78
|
+
120812,
|
|
79
|
+
120822,
|
|
80
|
+
123200,
|
|
81
|
+
123632,
|
|
82
|
+
124144,
|
|
83
|
+
124401,
|
|
84
|
+
125264,
|
|
85
|
+
130032
|
|
86
|
+
];
|
|
87
|
+
/**
|
|
88
|
+
* Normalizes non-ASCII decimal digits to ASCII digits.
|
|
89
|
+
* Converts characters from Unicode decimal digit blocks (e.g., Arabic-Indic,
|
|
90
|
+
* Devanagari, Bengali) to their ASCII equivalents (0-9).
|
|
91
|
+
*
|
|
92
|
+
* All current Unicode \p{Nd} blocks are included in decimalDigitBlockStarts.
|
|
93
|
+
*/
|
|
94
|
+
const normalizeDigits = (str) => str.replace(/* @__PURE__ */ new RegExp("\\p{Nd}", "gu"), (ch) => {
|
|
95
|
+
const cp = ch.codePointAt(0);
|
|
96
|
+
if (cp <= 57) return ch;
|
|
97
|
+
let lo = 0;
|
|
98
|
+
let hi = decimalDigitBlockStarts.length - 1;
|
|
99
|
+
while (lo < hi) {
|
|
100
|
+
const mid = lo + hi + 1 >>> 1;
|
|
101
|
+
if (decimalDigitBlockStarts[mid] <= cp) lo = mid;
|
|
102
|
+
else hi = mid - 1;
|
|
103
|
+
}
|
|
104
|
+
return String(cp - decimalDigitBlockStarts[lo]);
|
|
105
|
+
});
|
|
106
|
+
/**
|
|
3
107
|
* Map of Unicode fraction code points to their ASCII equivalents.
|
|
4
108
|
*/
|
|
5
109
|
const vulgarFractionToAsciiMap = {
|
|
@@ -48,15 +152,15 @@ const vulgarFractionToAsciiMap = {
|
|
|
48
152
|
* numericRegex.exec("2 / 3") // [ "2 / 3", "2", "/ 3", null ]
|
|
49
153
|
* ```
|
|
50
154
|
*/
|
|
51
|
-
const numericRegex =
|
|
155
|
+
const numericRegex = /^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[,_]\d|\d)*)*)(([eE][+-]?\d(?:[,_]\d|\d)*)?|\.\d(?:[,_]\d|\d)*([eE][+-]?\d(?:[,_]\d|\d)*)?|(\s+\d(?:[,_]\d|\d)*\s*)?\s*\/\s*\d(?:[,_]\d|\d)*)?$/;
|
|
52
156
|
/**
|
|
53
157
|
* Same as {@link numericRegex}, but allows (and ignores) trailing invalid characters.
|
|
54
158
|
*/
|
|
55
|
-
const numericRegexWithTrailingInvalid =
|
|
159
|
+
const numericRegexWithTrailingInvalid = /^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[,_]\d|\d)*)*)(([eE][+-]?\d(?:[,_]\d|\d)*)?|\.\d(?:[,_]\d|\d)*([eE][+-]?\d(?:[,_]\d|\d)*)?|(\s+\d(?:[,_]\d|\d)*\s*)?\s*\/\s*\d(?:[,_]\d|\d)*)?(?:\s*[^.\d/].*)?/;
|
|
56
160
|
/**
|
|
57
161
|
* Captures any Unicode vulgar fractions.
|
|
58
162
|
*/
|
|
59
|
-
const vulgarFractionsRegex =
|
|
163
|
+
const vulgarFractionsRegex = /([¼½¾⅐⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞⅟}])/g;
|
|
60
164
|
/**
|
|
61
165
|
* Map of Roman numeral sequences to their decimal equivalents.
|
|
62
166
|
*/
|
|
@@ -134,7 +238,7 @@ const romanNumeralUnicodeToAsciiMap = {
|
|
|
134
238
|
/**
|
|
135
239
|
* Captures all Unicode Roman numeral code points.
|
|
136
240
|
*/
|
|
137
|
-
const romanNumeralUnicodeRegex =
|
|
241
|
+
const romanNumeralUnicodeRegex = /([ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿ])/gi;
|
|
138
242
|
/**
|
|
139
243
|
* Captures a valid Roman numeral sequence.
|
|
140
244
|
*
|
|
@@ -156,7 +260,7 @@ const romanNumeralUnicodeRegex = new RegExp("([ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪ
|
|
|
156
260
|
* romanNumeralRegex.exec("MCCXIV") // [ "MCCXIV", "M", "CC", "X", "IV" ]
|
|
157
261
|
* ```
|
|
158
262
|
*/
|
|
159
|
-
const romanNumeralRegex =
|
|
263
|
+
const romanNumeralRegex = /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;
|
|
160
264
|
/**
|
|
161
265
|
* Default options for {@link numericQuantity}.
|
|
162
266
|
*/
|
|
@@ -187,18 +291,18 @@ const parseRomanNumerals = (romanNumerals) => {
|
|
|
187
291
|
};
|
|
188
292
|
|
|
189
293
|
//#endregion
|
|
190
|
-
//#region
|
|
294
|
+
//#region \0@oxc-project+runtime@0.112.0/helpers/typeof.js
|
|
191
295
|
function _typeof(o) {
|
|
192
296
|
"@babel/helpers - typeof";
|
|
193
|
-
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o
|
|
194
|
-
return typeof o
|
|
195
|
-
} : function(o
|
|
196
|
-
return o
|
|
297
|
+
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
|
|
298
|
+
return typeof o;
|
|
299
|
+
} : function(o) {
|
|
300
|
+
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
|
|
197
301
|
}, _typeof(o);
|
|
198
302
|
}
|
|
199
303
|
|
|
200
304
|
//#endregion
|
|
201
|
-
//#region
|
|
305
|
+
//#region \0@oxc-project+runtime@0.112.0/helpers/toPrimitive.js
|
|
202
306
|
function toPrimitive(t, r) {
|
|
203
307
|
if ("object" != _typeof(t) || !t) return t;
|
|
204
308
|
var e = t[Symbol.toPrimitive];
|
|
@@ -211,14 +315,14 @@ function toPrimitive(t, r) {
|
|
|
211
315
|
}
|
|
212
316
|
|
|
213
317
|
//#endregion
|
|
214
|
-
//#region
|
|
318
|
+
//#region \0@oxc-project+runtime@0.112.0/helpers/toPropertyKey.js
|
|
215
319
|
function toPropertyKey(t) {
|
|
216
320
|
var i = toPrimitive(t, "string");
|
|
217
321
|
return "symbol" == _typeof(i) ? i : i + "";
|
|
218
322
|
}
|
|
219
323
|
|
|
220
324
|
//#endregion
|
|
221
|
-
//#region
|
|
325
|
+
//#region \0@oxc-project+runtime@0.112.0/helpers/defineProperty.js
|
|
222
326
|
function _defineProperty(e, r, t) {
|
|
223
327
|
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
224
328
|
value: t,
|
|
@@ -229,13 +333,13 @@ function _defineProperty(e, r, t) {
|
|
|
229
333
|
}
|
|
230
334
|
|
|
231
335
|
//#endregion
|
|
232
|
-
//#region
|
|
336
|
+
//#region \0@oxc-project+runtime@0.112.0/helpers/objectSpread2.js
|
|
233
337
|
function ownKeys(e, r) {
|
|
234
338
|
var t = Object.keys(e);
|
|
235
339
|
if (Object.getOwnPropertySymbols) {
|
|
236
340
|
var o = Object.getOwnPropertySymbols(e);
|
|
237
|
-
r && (o = o.filter(function(r
|
|
238
|
-
return Object.getOwnPropertyDescriptor(e, r
|
|
341
|
+
r && (o = o.filter(function(r) {
|
|
342
|
+
return Object.getOwnPropertyDescriptor(e, r).enumerable;
|
|
239
343
|
})), t.push.apply(t, o);
|
|
240
344
|
}
|
|
241
345
|
return t;
|
|
@@ -243,10 +347,10 @@ function ownKeys(e, r) {
|
|
|
243
347
|
function _objectSpread2(e) {
|
|
244
348
|
for (var r = 1; r < arguments.length; r++) {
|
|
245
349
|
var t = null != arguments[r] ? arguments[r] : {};
|
|
246
|
-
r % 2 ? ownKeys(Object(t), !0).forEach(function(r
|
|
247
|
-
_defineProperty(e, r
|
|
248
|
-
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r
|
|
249
|
-
Object.defineProperty(e, r
|
|
350
|
+
r % 2 ? ownKeys(Object(t), !0).forEach(function(r) {
|
|
351
|
+
_defineProperty(e, r, t[r]);
|
|
352
|
+
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r) {
|
|
353
|
+
Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
|
|
250
354
|
});
|
|
251
355
|
}
|
|
252
356
|
return e;
|
|
@@ -258,7 +362,7 @@ const spaceThenSlashRegex = /^\s*\//;
|
|
|
258
362
|
function numericQuantity(quantity, options = defaultOptions) {
|
|
259
363
|
if (typeof quantity === "number" || typeof quantity === "bigint") return quantity;
|
|
260
364
|
let finalResult = NaN;
|
|
261
|
-
const quantityAsString = `${quantity}`.replace(vulgarFractionsRegex, (_m, vf) => ` ${vulgarFractionToAsciiMap[vf]}`).replace("⁄", "/").trim();
|
|
365
|
+
const quantityAsString = normalizeDigits(`${quantity}`.replace(vulgarFractionsRegex, (_m, vf) => ` ${vulgarFractionToAsciiMap[vf]}`).replace("⁄", "/").trim());
|
|
262
366
|
if (quantityAsString.length === 0) return NaN;
|
|
263
367
|
const opts = _objectSpread2(_objectSpread2({}, defaultOptions), options);
|
|
264
368
|
let normalizedString = quantityAsString;
|
|
@@ -297,13 +401,12 @@ function numericQuantity(quantity, options = defaultOptions) {
|
|
|
297
401
|
const denominator = parseInt(numberGroup2.replace("/", ""));
|
|
298
402
|
finalResult = isNaN(roundingFactor) ? numerator / denominator : Math.round(numerator * roundingFactor / denominator) / roundingFactor;
|
|
299
403
|
} else {
|
|
300
|
-
const
|
|
301
|
-
const [numerator, denominator] = fractionArray.map((v) => parseInt(v));
|
|
404
|
+
const [numerator, denominator] = numberGroup2.split("/").map((v) => parseInt(v));
|
|
302
405
|
finalResult += isNaN(roundingFactor) ? numerator / denominator : Math.round(numerator * roundingFactor / denominator) / roundingFactor;
|
|
303
406
|
}
|
|
304
407
|
return dash ? finalResult * -1 : finalResult;
|
|
305
408
|
}
|
|
306
409
|
|
|
307
410
|
//#endregion
|
|
308
|
-
export { defaultOptions, numericQuantity, numericRegex, numericRegexWithTrailingInvalid, parseRomanNumerals, romanNumeralRegex, romanNumeralUnicodeRegex, romanNumeralUnicodeToAsciiMap, romanNumeralValues, vulgarFractionToAsciiMap, vulgarFractionsRegex };
|
|
411
|
+
export { defaultOptions, normalizeDigits, numericQuantity, numericRegex, numericRegexWithTrailingInvalid, parseRomanNumerals, romanNumeralRegex, romanNumeralUnicodeRegex, romanNumeralUnicodeToAsciiMap, romanNumeralValues, vulgarFractionToAsciiMap, vulgarFractionsRegex };
|
|
309
412
|
//# sourceMappingURL=numeric-quantity.legacy-esm.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"numeric-quantity.legacy-esm.js","names":["vulgarFractionToAsciiMap: Record<\n VulgarFraction,\n `${number}/${number | ''}`\n>","numericRegex: RegExp","numericRegexWithTrailingInvalid: RegExp","vulgarFractionsRegex: RegExp","romanNumeralValues: {\n [k in RomanNumeralSequenceFragment]?: number;\n}","romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n>","romanNumeralUnicodeRegex: RegExp","romanNumeralRegex: RegExp","defaultOptions: Required<NumericQuantityOptions>","o","r","opts: Required<NumericQuantityOptions>"],"sources":["../src/constants.ts","../src/parseRomanNumerals.ts","../node_modules/@oxc-project/runtime/src/helpers/esm/typeof.js","../node_modules/@oxc-project/runtime/src/helpers/esm/toPrimitive.js","../node_modules/@oxc-project/runtime/src/helpers/esm/toPropertyKey.js","../node_modules/@oxc-project/runtime/src/helpers/esm/defineProperty.js","../node_modules/@oxc-project/runtime/src/helpers/esm/objectSpread2.js","../src/numericQuantity.ts"],"sourcesContent":["import type {\n NumericQuantityOptions,\n RomanNumeralAscii,\n RomanNumeralUnicode,\n VulgarFraction,\n} from './types';\n\n// #region Arabic numerals\n/**\n * Map of Unicode fraction code points to their ASCII equivalents.\n */\nexport const vulgarFractionToAsciiMap: Record<\n VulgarFraction,\n `${number}/${number | ''}`\n> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '⅟': '1/',\n} as const;\n\n/**\n * Captures the individual elements of a numeric string. Commas and underscores are allowed\n * as separators, as long as they appear between digits and are not consecutive.\n *\n * Capture groups:\n *\n * | # | Description | Example(s) |\n * | --- | ------------------------------------------------ | ------------------------------------------------------------------- |\n * | `0` | entire string | `\"2 1/3\"` from `\"2 1/3\"` |\n * | `1` | \"negative\" dash | `\"-\"` from `\"-2 1/3\"` |\n * | `2` | whole number or numerator | `\"2\"` from `\"2 1/3\"`; `\"1\"` from `\"1/3\"` |\n * | `3` | entire fraction, decimal portion, or denominator | `\" 1/3\"` from `\"2 1/3\"`; `\".33\"` from `\"2.33\"`; `\"/3\"` from `\"1/3\"` |\n *\n * _Capture group 2 may include comma/underscore separators._\n *\n * @example\n *\n * ```ts\n * numericRegex.exec(\"1\") // [ \"1\", \"1\", null, null ]\n * numericRegex.exec(\"1.23\") // [ \"1.23\", \"1\", \".23\", null ]\n * numericRegex.exec(\"1 2/3\") // [ \"1 2/3\", \"1\", \" 2/3\", \" 2\" ]\n * numericRegex.exec(\"2/3\") // [ \"2/3\", \"2\", \"/3\", null ]\n * numericRegex.exec(\"2 / 3\") // [ \"2 / 3\", \"2\", \"/ 3\", null ]\n * ```\n */\nexport const numericRegex: RegExp =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[,_]\\d|\\d)*)*)(([eE][+-]?\\d(?:[,_]\\d|\\d)*)?|\\.\\d(?:[,_]\\d|\\d)*([eE][+-]?\\d(?:[,_]\\d|\\d)*)?|(\\s+\\d(?:[,_]\\d|\\d)*\\s*)?\\s*\\/\\s*\\d(?:[,_]\\d|\\d)*)?$/;\n/**\n * Same as {@link numericRegex}, but allows (and ignores) trailing invalid characters.\n */\nexport const numericRegexWithTrailingInvalid: RegExp =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[,_]\\d|\\d)*)*)(([eE][+-]?\\d(?:[,_]\\d|\\d)*)?|\\.\\d(?:[,_]\\d|\\d)*([eE][+-]?\\d(?:[,_]\\d|\\d)*)?|(\\s+\\d(?:[,_]\\d|\\d)*\\s*)?\\s*\\/\\s*\\d(?:[,_]\\d|\\d)*)?(?:\\s*[^.\\d/].*)?/;\n\n/**\n * Captures any Unicode vulgar fractions.\n */\nexport const vulgarFractionsRegex: RegExp = /([¼½¾⅐⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞⅟}])/g;\n// #endregion\n\n// #region Roman numerals\ntype RomanNumeralSequenceFragment =\n | `${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;\n\n/**\n * Map of Roman numeral sequences to their decimal equivalents.\n */\nexport const romanNumeralValues: {\n [k in RomanNumeralSequenceFragment]?: number;\n} = {\n MMM: 3000,\n MM: 2000,\n M: 1000,\n CM: 900,\n DCCC: 800,\n DCC: 700,\n DC: 600,\n D: 500,\n CD: 400,\n CCC: 300,\n CC: 200,\n C: 100,\n XC: 90,\n LXXX: 80,\n LXX: 70,\n LX: 60,\n L: 50,\n XL: 40,\n XXX: 30,\n XX: 20,\n XII: 12, // only here for tests; not used in practice\n XI: 11, // only here for tests; not used in practice\n X: 10,\n IX: 9,\n VIII: 8,\n VII: 7,\n VI: 6,\n V: 5,\n IV: 4,\n III: 3,\n II: 2,\n I: 1,\n} as const;\n\n/**\n * Map of Unicode Roman numeral code points to their ASCII equivalents.\n */\nexport const romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n> = {\n // Roman Numeral One (U+2160)\n Ⅰ: 'I',\n // Roman Numeral Two (U+2161)\n Ⅱ: 'II',\n // Roman Numeral Three (U+2162)\n Ⅲ: 'III',\n // Roman Numeral Four (U+2163)\n Ⅳ: 'IV',\n // Roman Numeral Five (U+2164)\n Ⅴ: 'V',\n // Roman Numeral Six (U+2165)\n Ⅵ: 'VI',\n // Roman Numeral Seven (U+2166)\n Ⅶ: 'VII',\n // Roman Numeral Eight (U+2167)\n Ⅷ: 'VIII',\n // Roman Numeral Nine (U+2168)\n Ⅸ: 'IX',\n // Roman Numeral Ten (U+2169)\n Ⅹ: 'X',\n // Roman Numeral Eleven (U+216A)\n Ⅺ: 'XI',\n // Roman Numeral Twelve (U+216B)\n Ⅻ: 'XII',\n // Roman Numeral Fifty (U+216C)\n Ⅼ: 'L',\n // Roman Numeral One Hundred (U+216D)\n Ⅽ: 'C',\n // Roman Numeral Five Hundred (U+216E)\n Ⅾ: 'D',\n // Roman Numeral One Thousand (U+216F)\n Ⅿ: 'M',\n // Small Roman Numeral One (U+2170)\n ⅰ: 'I',\n // Small Roman Numeral Two (U+2171)\n ⅱ: 'II',\n // Small Roman Numeral Three (U+2172)\n ⅲ: 'III',\n // Small Roman Numeral Four (U+2173)\n ⅳ: 'IV',\n // Small Roman Numeral Five (U+2174)\n ⅴ: 'V',\n // Small Roman Numeral Six (U+2175)\n ⅵ: 'VI',\n // Small Roman Numeral Seven (U+2176)\n ⅶ: 'VII',\n // Small Roman Numeral Eight (U+2177)\n ⅷ: 'VIII',\n // Small Roman Numeral Nine (U+2178)\n ⅸ: 'IX',\n // Small Roman Numeral Ten (U+2179)\n ⅹ: 'X',\n // Small Roman Numeral Eleven (U+217A)\n ⅺ: 'XI',\n // Small Roman Numeral Twelve (U+217B)\n ⅻ: 'XII',\n // Small Roman Numeral Fifty (U+217C)\n ⅼ: 'L',\n // Small Roman Numeral One Hundred (U+217D)\n ⅽ: 'C',\n // Small Roman Numeral Five Hundred (U+217E)\n ⅾ: 'D',\n // Small Roman Numeral One Thousand (U+217F)\n ⅿ: 'M',\n} as const;\n\n/**\n * Captures all Unicode Roman numeral code points.\n */\nexport const romanNumeralUnicodeRegex: RegExp =\n /([ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿ])/gi;\n\n/**\n * Captures a valid Roman numeral sequence.\n *\n * Capture groups:\n *\n * | # | Description | Example |\n * | --- | --------------- | ------------------------ |\n * | `0` | Entire string | \"MCCXIV\" from \"MCCXIV\" |\n * | `1` | Thousands | \"M\" from \"MCCXIV\" |\n * | `2` | Hundreds | \"CC\" from \"MCCXIV\" |\n * | `3` | Tens | \"X\" from \"MCCXIV\" |\n * | `4` | Ones | \"IV\" from \"MCCXIV\" |\n *\n * @example\n *\n * ```ts\n * romanNumeralRegex.exec(\"M\") // [ \"M\", \"M\", \"\", \"\", \"\" ]\n * romanNumeralRegex.exec(\"XII\") // [ \"XII\", \"\", \"\", \"X\", \"II\" ]\n * romanNumeralRegex.exec(\"MCCXIV\") // [ \"MCCXIV\", \"M\", \"CC\", \"X\", \"IV\" ]\n * ```\n */\nexport const romanNumeralRegex: RegExp =\n /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;\n// #endregion\n\n/**\n * Default options for {@link numericQuantity}.\n */\nexport const defaultOptions: Required<NumericQuantityOptions> = {\n round: 3,\n allowTrailingInvalid: false,\n romanNumerals: false,\n bigIntOnOverflow: false,\n decimalSeparator: '.',\n} as const;\n","import {\n romanNumeralRegex,\n romanNumeralUnicodeRegex,\n romanNumeralUnicodeToAsciiMap,\n romanNumeralValues,\n} from './constants';\n\n// Just a shorthand type alias\ntype RNV = keyof typeof romanNumeralValues;\n\n/**\n * Converts a string of Roman numerals to a number, like `parseInt`\n * for Roman numerals. Uses modern, strict rules (only 1 to 3999).\n *\n * The string can include ASCII representations of Roman numerals\n * or Unicode Roman numeral code points (`U+2160` through `U+217F`).\n */\nexport const parseRomanNumerals = (romanNumerals: string): number => {\n const normalized = `${romanNumerals}`\n // Convert Unicode Roman numerals to ASCII\n .replace(\n romanNumeralUnicodeRegex,\n (_m, rn: keyof typeof romanNumeralUnicodeToAsciiMap) =>\n romanNumeralUnicodeToAsciiMap[rn]\n )\n // Normalize to uppercase (more common for Roman numerals)\n .toUpperCase();\n\n const regexResult = romanNumeralRegex.exec(normalized);\n\n if (!regexResult) {\n return NaN;\n }\n\n const [, thousands, hundreds, tens, ones] = regexResult;\n\n return (\n (romanNumeralValues[thousands as RNV] ?? 0) +\n (romanNumeralValues[hundreds as RNV] ?? 0) +\n (romanNumeralValues[tens as RNV] ?? 0) +\n (romanNumeralValues[ones as RNV] ?? 0)\n );\n};\n","function _typeof(o) {\n \"@babel/helpers - typeof\";\n\n return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) {\n return typeof o;\n } : function (o) {\n return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o;\n }, _typeof(o);\n}\nexport { _typeof as default };","import _typeof from \"./typeof.js\";\nfunction toPrimitive(t, r) {\n if (\"object\" != _typeof(t) || !t) return t;\n var e = t[Symbol.toPrimitive];\n if (void 0 !== e) {\n var i = e.call(t, r || \"default\");\n if (\"object\" != _typeof(i)) return i;\n throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n }\n return (\"string\" === r ? String : Number)(t);\n}\nexport { toPrimitive as default };","import _typeof from \"./typeof.js\";\nimport toPrimitive from \"./toPrimitive.js\";\nfunction toPropertyKey(t) {\n var i = toPrimitive(t, \"string\");\n return \"symbol\" == _typeof(i) ? i : i + \"\";\n}\nexport { toPropertyKey as default };","import toPropertyKey from \"./toPropertyKey.js\";\nfunction _defineProperty(e, r, t) {\n return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {\n value: t,\n enumerable: !0,\n configurable: !0,\n writable: !0\n }) : e[r] = t, e;\n}\nexport { _defineProperty as default };","import defineProperty from \"./defineProperty.js\";\nfunction ownKeys(e, r) {\n var t = Object.keys(e);\n if (Object.getOwnPropertySymbols) {\n var o = Object.getOwnPropertySymbols(e);\n r && (o = o.filter(function (r) {\n return Object.getOwnPropertyDescriptor(e, r).enumerable;\n })), t.push.apply(t, o);\n }\n return t;\n}\nfunction _objectSpread2(e) {\n for (var r = 1; r < arguments.length; r++) {\n var t = null != arguments[r] ? arguments[r] : {};\n r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {\n defineProperty(e, r, t[r]);\n }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {\n Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));\n });\n }\n return e;\n}\nexport { _objectSpread2 as default };","import {\n defaultOptions,\n numericRegex,\n numericRegexWithTrailingInvalid,\n vulgarFractionToAsciiMap,\n vulgarFractionsRegex,\n} from './constants';\nimport { parseRomanNumerals } from './parseRomanNumerals';\nimport type { NumericQuantityOptions } from './types';\n\nconst spaceThenSlashRegex = /^\\s*\\//;\n\n/**\n * Converts a string to a number, like an enhanced version of `parseFloat`.\n *\n * The string can include mixed numbers, vulgar fractions, or Roman numerals.\n */\nfunction numericQuantity(quantity: string | number): number;\nfunction numericQuantity(\n quantity: string | number,\n options: NumericQuantityOptions & { bigIntOnOverflow: true }\n): number | bigint;\nfunction numericQuantity(\n quantity: string | number,\n options?: NumericQuantityOptions\n): number;\nfunction numericQuantity(\n quantity: string | number,\n options: NumericQuantityOptions = defaultOptions\n) {\n if (typeof quantity === 'number' || typeof quantity === 'bigint') {\n return quantity;\n }\n\n let finalResult = NaN;\n\n // Coerce to string in case qty is a number\n const quantityAsString = `${quantity}`\n // Convert vulgar fractions to ASCII, with a leading space\n // to keep the whole number and the fraction separate\n .replace(\n vulgarFractionsRegex,\n (_m, vf: keyof typeof vulgarFractionToAsciiMap) =>\n ` ${vulgarFractionToAsciiMap[vf]}`\n )\n // Convert fraction slash to standard slash\n .replace('⁄', '/')\n .trim();\n\n // Bail out if the string was only white space\n if (quantityAsString.length === 0) {\n return NaN;\n }\n\n const opts: Required<NumericQuantityOptions> = {\n ...defaultOptions,\n ...options,\n };\n\n let normalizedString = quantityAsString;\n\n if (opts.decimalSeparator === ',') {\n const commaCount = (quantityAsString.match(/,/g) || []).length;\n if (commaCount === 1) {\n // Treat lone comma as decimal separator; remove all \".\" since they represent\n // thousands/whatever separators\n normalizedString = quantityAsString\n .replaceAll('.', '_')\n .replace(',', '.');\n } else if (commaCount > 1) {\n // The second comma and everything after is \"trailing invalid\"\n if (!opts.allowTrailingInvalid) {\n // Bail out if trailing invalid is not allowed\n return NaN;\n }\n\n const firstCommaIndex = quantityAsString.indexOf(',');\n const secondCommaIndex = quantityAsString.indexOf(\n ',',\n firstCommaIndex + 1\n );\n const beforeSecondComma = quantityAsString\n .substring(0, secondCommaIndex)\n .replaceAll('.', '_')\n .replace(',', '.');\n const afterSecondComma = quantityAsString.substring(secondCommaIndex + 1);\n normalizedString = opts.allowTrailingInvalid\n ? beforeSecondComma + '&' + afterSecondComma\n : beforeSecondComma;\n } else {\n // No comma as decimal separator, so remove all \".\" since they represent\n // thousands/whatever separators\n normalizedString = quantityAsString.replaceAll('.', '_');\n }\n }\n\n const regexResult = (\n opts.allowTrailingInvalid ? numericRegexWithTrailingInvalid : numericRegex\n ).exec(normalizedString);\n\n // If the Arabic numeral regex fails, try Roman numerals\n if (!regexResult) {\n return opts.romanNumerals ? parseRomanNumerals(quantityAsString) : NaN;\n }\n\n const [, dash, ng1temp, ng2temp] = regexResult;\n const numberGroup1 = ng1temp.replaceAll(',', '').replaceAll('_', '');\n const numberGroup2 = ng2temp?.replaceAll(',', '').replaceAll('_', '');\n\n // Numerify capture group 1\n if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith('.')) {\n finalResult = 0;\n } else {\n if (opts.bigIntOnOverflow) {\n const asBigInt = dash ? BigInt(`-${numberGroup1}`) : BigInt(numberGroup1);\n if (\n asBigInt > BigInt(Number.MAX_SAFE_INTEGER) ||\n asBigInt < BigInt(Number.MIN_SAFE_INTEGER)\n ) {\n return asBigInt;\n }\n }\n\n finalResult = parseInt(numberGroup1);\n }\n\n // If capture group 2 is null, then we're dealing with an integer\n // and there is nothing left to process\n if (!numberGroup2) {\n return dash ? finalResult * -1 : finalResult;\n }\n\n const roundingFactor =\n opts.round === false\n ? NaN\n : parseFloat(`1e${Math.floor(Math.max(0, opts.round))}`);\n\n if (\n numberGroup2.startsWith('.') ||\n numberGroup2.startsWith('e') ||\n numberGroup2.startsWith('E')\n ) {\n // If first char of `numberGroup2` is \".\" or \"e\"/\"E\", it's a decimal\n const decimalValue = parseFloat(`${finalResult}${numberGroup2}`);\n finalResult = isNaN(roundingFactor)\n ? decimalValue\n : Math.round(decimalValue * roundingFactor) / roundingFactor;\n } else if (spaceThenSlashRegex.test(numberGroup2)) {\n // If the first non-space char is \"/\" it's a pure fraction (e.g. \"1/2\")\n const numerator = parseInt(numberGroup1);\n const denominator = parseInt(numberGroup2.replace('/', ''));\n finalResult = isNaN(roundingFactor)\n ? numerator / denominator\n : Math.round((numerator * roundingFactor) / denominator) / roundingFactor;\n } else {\n // Otherwise it's a mixed fraction (e.g. \"1 2/3\")\n const fractionArray = numberGroup2.split('/');\n const [numerator, denominator] = fractionArray.map(v => parseInt(v));\n finalResult += isNaN(roundingFactor)\n ? numerator / denominator\n : Math.round((numerator * roundingFactor) / denominator) / roundingFactor;\n }\n\n return dash ? finalResult * -1 : finalResult;\n}\n\nexport { numericQuantity };\n"],"x_google_ignoreList":[2,3,4,5,6],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"numeric-quantity.legacy-esm.js","names":[],"sources":["../src/constants.ts","../src/parseRomanNumerals.ts","../src/numericQuantity.ts"],"sourcesContent":["import type {\n NumericQuantityOptions,\n RomanNumeralAscii,\n RomanNumeralUnicode,\n VulgarFraction,\n} from './types';\n\n// #region Decimal_Number Unicode category\n\n/**\n * Unicode decimal digit block start code points.\n * Each block contains 10 contiguous digits (0-9).\n * This list covers all \\p{Nd} (Decimal_Number) blocks through Unicode 17.0.\n * The drift test in index.test.ts validates completeness against the JS engine.\n */\nconst decimalDigitBlockStarts = [\n 0x0030, // ASCII (0-9)\n 0x0660, // Arabic-Indic\n 0x06f0, // Extended Arabic-Indic (Persian/Urdu)\n 0x07c0, // NKo\n 0x0966, // Devanagari\n 0x09e6, // Bengali\n 0x0a66, // Gurmukhi\n 0x0ae6, // Gujarati\n 0x0b66, // Oriya\n 0x0be6, // Tamil\n 0x0c66, // Telugu\n 0x0ce6, // Kannada\n 0x0d66, // Malayalam\n 0x0de6, // Sinhala Lith\n 0x0e50, // Thai\n 0x0ed0, // Lao\n 0x0f20, // Tibetan\n 0x1040, // Myanmar\n 0x1090, // Myanmar Shan\n 0x17e0, // Khmer\n 0x1810, // Mongolian\n 0x1946, // Limbu\n 0x19d0, // New Tai Lue\n 0x1a80, // Tai Tham Hora\n 0x1a90, // Tai Tham Tham\n 0x1b50, // Balinese\n 0x1bb0, // Sundanese\n 0x1c40, // Lepcha\n 0x1c50, // Ol Chiki\n 0xa620, // Vai\n 0xa8d0, // Saurashtra\n 0xa900, // Kayah Li\n 0xa9d0, // Javanese\n 0xa9f0, // Myanmar Tai Laing\n 0xaa50, // Cham\n 0xabf0, // Meetei Mayek\n 0xff10, // Fullwidth\n 0x104a0, // Osmanya\n 0x10d30, // Hanifi Rohingya\n 0x10d40, // Garay\n 0x11066, // Brahmi\n 0x110f0, // Sora Sompeng\n 0x11136, // Chakma\n 0x111d0, // Sharada\n 0x112f0, // Khudawadi\n 0x11450, // Newa\n 0x114d0, // Tirhuta\n 0x11650, // Modi\n 0x116c0, // Takri\n 0x116d0, // Myanmar Pao\n 0x116da, // Myanmar Eastern Pwo Karen\n 0x11730, // Ahom\n 0x118e0, // Warang Citi\n 0x11950, // Dives Akuru\n 0x11bf0, // Sunuwar\n 0x11c50, // Bhaiksuki\n 0x11d50, // Masaram Gondi\n 0x11da0, // Gunjala Gondi\n 0x11de0, // Tolong Siki\n 0x11f50, // Kawi\n 0x16130, // Gurung Khema\n 0x16a60, // Mro\n 0x16ac0, // Tangsa\n 0x16b50, // Pahawh Hmong\n 0x16d70, // Kirat Rai\n 0x1ccf0, // Outlined Digits\n 0x1d7ce, // Mathematical Bold\n 0x1d7d8, // Mathematical Double-Struck\n 0x1d7e2, // Mathematical Sans-Serif\n 0x1d7ec, // Mathematical Sans-Serif Bold\n 0x1d7f6, // Mathematical Monospace\n 0x1e140, // Nyiakeng Puachue Hmong\n 0x1e2f0, // Wancho\n 0x1e4f0, // Nag Mundari\n 0x1e5f1, // Ol Onal\n 0x1e950, // Adlam\n 0x1fbf0, // Segmented Digits\n] as const;\n\n/**\n * Normalizes non-ASCII decimal digits to ASCII digits.\n * Converts characters from Unicode decimal digit blocks (e.g., Arabic-Indic,\n * Devanagari, Bengali) to their ASCII equivalents (0-9).\n *\n * All current Unicode \\p{Nd} blocks are included in decimalDigitBlockStarts.\n */\nexport const normalizeDigits = (str: string): string =>\n str.replace(/\\p{Nd}/gu, ch => {\n const cp = ch.codePointAt(0)!;\n // ASCII digits (0x0030-0x0039) don't need conversion\n if (cp <= 0x39) return ch;\n // Binary search for the largest block start ≤ cp\n let lo = 0;\n let hi = decimalDigitBlockStarts.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >>> 1;\n if (decimalDigitBlockStarts[mid] <= cp) {\n lo = mid;\n } else {\n hi = mid - 1;\n }\n }\n return String(cp - decimalDigitBlockStarts[lo]!);\n });\n\n/**\n * Map of Unicode fraction code points to their ASCII equivalents.\n */\nexport const vulgarFractionToAsciiMap: Record<\n VulgarFraction,\n `${number}/${number | ''}`\n> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '⅟': '1/',\n} as const;\n\n/**\n * Captures the individual elements of a numeric string. Commas and underscores are allowed\n * as separators, as long as they appear between digits and are not consecutive.\n *\n * Capture groups:\n *\n * | # | Description | Example(s) |\n * | --- | ------------------------------------------------ | ------------------------------------------------------------------- |\n * | `0` | entire string | `\"2 1/3\"` from `\"2 1/3\"` |\n * | `1` | \"negative\" dash | `\"-\"` from `\"-2 1/3\"` |\n * | `2` | whole number or numerator | `\"2\"` from `\"2 1/3\"`; `\"1\"` from `\"1/3\"` |\n * | `3` | entire fraction, decimal portion, or denominator | `\" 1/3\"` from `\"2 1/3\"`; `\".33\"` from `\"2.33\"`; `\"/3\"` from `\"1/3\"` |\n *\n * _Capture group 2 may include comma/underscore separators._\n *\n * @example\n *\n * ```ts\n * numericRegex.exec(\"1\") // [ \"1\", \"1\", null, null ]\n * numericRegex.exec(\"1.23\") // [ \"1.23\", \"1\", \".23\", null ]\n * numericRegex.exec(\"1 2/3\") // [ \"1 2/3\", \"1\", \" 2/3\", \" 2\" ]\n * numericRegex.exec(\"2/3\") // [ \"2/3\", \"2\", \"/3\", null ]\n * numericRegex.exec(\"2 / 3\") // [ \"2 / 3\", \"2\", \"/ 3\", null ]\n * ```\n */\nexport const numericRegex: RegExp =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[,_]\\d|\\d)*)*)(([eE][+-]?\\d(?:[,_]\\d|\\d)*)?|\\.\\d(?:[,_]\\d|\\d)*([eE][+-]?\\d(?:[,_]\\d|\\d)*)?|(\\s+\\d(?:[,_]\\d|\\d)*\\s*)?\\s*\\/\\s*\\d(?:[,_]\\d|\\d)*)?$/;\n/**\n * Same as {@link numericRegex}, but allows (and ignores) trailing invalid characters.\n */\nexport const numericRegexWithTrailingInvalid: RegExp =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[,_]\\d|\\d)*)*)(([eE][+-]?\\d(?:[,_]\\d|\\d)*)?|\\.\\d(?:[,_]\\d|\\d)*([eE][+-]?\\d(?:[,_]\\d|\\d)*)?|(\\s+\\d(?:[,_]\\d|\\d)*\\s*)?\\s*\\/\\s*\\d(?:[,_]\\d|\\d)*)?(?:\\s*[^.\\d/].*)?/;\n\n/**\n * Captures any Unicode vulgar fractions.\n */\nexport const vulgarFractionsRegex: RegExp = /([¼½¾⅐⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞⅟}])/g;\n\n// #endregion\n\n// #region Roman numerals\n\ntype RomanNumeralSequenceFragment =\n | `${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;\n\n/**\n * Map of Roman numeral sequences to their decimal equivalents.\n */\nexport const romanNumeralValues: {\n [k in RomanNumeralSequenceFragment]?: number;\n} = {\n MMM: 3000,\n MM: 2000,\n M: 1000,\n CM: 900,\n DCCC: 800,\n DCC: 700,\n DC: 600,\n D: 500,\n CD: 400,\n CCC: 300,\n CC: 200,\n C: 100,\n XC: 90,\n LXXX: 80,\n LXX: 70,\n LX: 60,\n L: 50,\n XL: 40,\n XXX: 30,\n XX: 20,\n XII: 12, // only here for tests; not used in practice\n XI: 11, // only here for tests; not used in practice\n X: 10,\n IX: 9,\n VIII: 8,\n VII: 7,\n VI: 6,\n V: 5,\n IV: 4,\n III: 3,\n II: 2,\n I: 1,\n} as const;\n\n/**\n * Map of Unicode Roman numeral code points to their ASCII equivalents.\n */\nexport const romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n> = {\n // Roman Numeral One (U+2160)\n Ⅰ: 'I',\n // Roman Numeral Two (U+2161)\n Ⅱ: 'II',\n // Roman Numeral Three (U+2162)\n Ⅲ: 'III',\n // Roman Numeral Four (U+2163)\n Ⅳ: 'IV',\n // Roman Numeral Five (U+2164)\n Ⅴ: 'V',\n // Roman Numeral Six (U+2165)\n Ⅵ: 'VI',\n // Roman Numeral Seven (U+2166)\n Ⅶ: 'VII',\n // Roman Numeral Eight (U+2167)\n Ⅷ: 'VIII',\n // Roman Numeral Nine (U+2168)\n Ⅸ: 'IX',\n // Roman Numeral Ten (U+2169)\n Ⅹ: 'X',\n // Roman Numeral Eleven (U+216A)\n Ⅺ: 'XI',\n // Roman Numeral Twelve (U+216B)\n Ⅻ: 'XII',\n // Roman Numeral Fifty (U+216C)\n Ⅼ: 'L',\n // Roman Numeral One Hundred (U+216D)\n Ⅽ: 'C',\n // Roman Numeral Five Hundred (U+216E)\n Ⅾ: 'D',\n // Roman Numeral One Thousand (U+216F)\n Ⅿ: 'M',\n // Small Roman Numeral One (U+2170)\n ⅰ: 'I',\n // Small Roman Numeral Two (U+2171)\n ⅱ: 'II',\n // Small Roman Numeral Three (U+2172)\n ⅲ: 'III',\n // Small Roman Numeral Four (U+2173)\n ⅳ: 'IV',\n // Small Roman Numeral Five (U+2174)\n ⅴ: 'V',\n // Small Roman Numeral Six (U+2175)\n ⅵ: 'VI',\n // Small Roman Numeral Seven (U+2176)\n ⅶ: 'VII',\n // Small Roman Numeral Eight (U+2177)\n ⅷ: 'VIII',\n // Small Roman Numeral Nine (U+2178)\n ⅸ: 'IX',\n // Small Roman Numeral Ten (U+2179)\n ⅹ: 'X',\n // Small Roman Numeral Eleven (U+217A)\n ⅺ: 'XI',\n // Small Roman Numeral Twelve (U+217B)\n ⅻ: 'XII',\n // Small Roman Numeral Fifty (U+217C)\n ⅼ: 'L',\n // Small Roman Numeral One Hundred (U+217D)\n ⅽ: 'C',\n // Small Roman Numeral Five Hundred (U+217E)\n ⅾ: 'D',\n // Small Roman Numeral One Thousand (U+217F)\n ⅿ: 'M',\n} as const;\n\n/**\n * Captures all Unicode Roman numeral code points.\n */\nexport const romanNumeralUnicodeRegex: RegExp =\n /([ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿ])/gi;\n\n/**\n * Captures a valid Roman numeral sequence.\n *\n * Capture groups:\n *\n * | # | Description | Example |\n * | --- | --------------- | ------------------------ |\n * | `0` | Entire string | \"MCCXIV\" from \"MCCXIV\" |\n * | `1` | Thousands | \"M\" from \"MCCXIV\" |\n * | `2` | Hundreds | \"CC\" from \"MCCXIV\" |\n * | `3` | Tens | \"X\" from \"MCCXIV\" |\n * | `4` | Ones | \"IV\" from \"MCCXIV\" |\n *\n * @example\n *\n * ```ts\n * romanNumeralRegex.exec(\"M\") // [ \"M\", \"M\", \"\", \"\", \"\" ]\n * romanNumeralRegex.exec(\"XII\") // [ \"XII\", \"\", \"\", \"X\", \"II\" ]\n * romanNumeralRegex.exec(\"MCCXIV\") // [ \"MCCXIV\", \"M\", \"CC\", \"X\", \"IV\" ]\n * ```\n */\nexport const romanNumeralRegex: RegExp =\n /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;\n\n// #endregion\n\n/**\n * Default options for {@link numericQuantity}.\n */\nexport const defaultOptions: Required<NumericQuantityOptions> = {\n round: 3,\n allowTrailingInvalid: false,\n romanNumerals: false,\n bigIntOnOverflow: false,\n decimalSeparator: '.',\n} as const;\n","import {\n romanNumeralRegex,\n romanNumeralUnicodeRegex,\n romanNumeralUnicodeToAsciiMap,\n romanNumeralValues,\n} from './constants';\n\n// Just a shorthand type alias\ntype RNV = keyof typeof romanNumeralValues;\n\n/**\n * Converts a string of Roman numerals to a number, like `parseInt`\n * for Roman numerals. Uses modern, strict rules (only 1 to 3999).\n *\n * The string can include ASCII representations of Roman numerals\n * or Unicode Roman numeral code points (`U+2160` through `U+217F`).\n */\nexport const parseRomanNumerals = (romanNumerals: string): number => {\n const normalized = `${romanNumerals}`\n // Convert Unicode Roman numerals to ASCII\n .replace(\n romanNumeralUnicodeRegex,\n (_m, rn: keyof typeof romanNumeralUnicodeToAsciiMap) =>\n romanNumeralUnicodeToAsciiMap[rn]\n )\n // Normalize to uppercase (more common for Roman numerals)\n .toUpperCase();\n\n const regexResult = romanNumeralRegex.exec(normalized);\n\n if (!regexResult) {\n return NaN;\n }\n\n const [, thousands, hundreds, tens, ones] = regexResult;\n\n return (\n (romanNumeralValues[thousands as RNV] ?? 0) +\n (romanNumeralValues[hundreds as RNV] ?? 0) +\n (romanNumeralValues[tens as RNV] ?? 0) +\n (romanNumeralValues[ones as RNV] ?? 0)\n );\n};\n","import {\n defaultOptions,\n normalizeDigits,\n numericRegex,\n numericRegexWithTrailingInvalid,\n vulgarFractionToAsciiMap,\n vulgarFractionsRegex,\n} from './constants';\nimport { parseRomanNumerals } from './parseRomanNumerals';\nimport type { NumericQuantityOptions } from './types';\n\nconst spaceThenSlashRegex = /^\\s*\\//;\n\n/**\n * Converts a string to a number, like an enhanced version of `parseFloat`.\n *\n * The string can include mixed numbers, vulgar fractions, or Roman numerals.\n */\nfunction numericQuantity(quantity: string | number): number;\nfunction numericQuantity(\n quantity: string | number,\n options: NumericQuantityOptions & { bigIntOnOverflow: true }\n): number | bigint;\nfunction numericQuantity(\n quantity: string | number,\n options?: NumericQuantityOptions\n): number;\nfunction numericQuantity(\n quantity: string | number,\n options: NumericQuantityOptions = defaultOptions\n) {\n if (typeof quantity === 'number' || typeof quantity === 'bigint') {\n return quantity;\n }\n\n let finalResult = NaN;\n\n // Coerce to string in case qty is a number\n const quantityAsString = normalizeDigits(\n `${quantity}`\n // Convert vulgar fractions to ASCII, with a leading space\n // to keep the whole number and the fraction separate\n .replace(\n vulgarFractionsRegex,\n (_m, vf: keyof typeof vulgarFractionToAsciiMap) =>\n ` ${vulgarFractionToAsciiMap[vf]}`\n )\n // Convert fraction slash to standard slash\n .replace('⁄', '/')\n .trim()\n );\n\n // Bail out if the string was only white space\n if (quantityAsString.length === 0) {\n return NaN;\n }\n\n const opts: Required<NumericQuantityOptions> = {\n ...defaultOptions,\n ...options,\n };\n\n let normalizedString = quantityAsString;\n\n if (opts.decimalSeparator === ',') {\n const commaCount = (quantityAsString.match(/,/g) || []).length;\n if (commaCount === 1) {\n // Treat lone comma as decimal separator; remove all \".\" since they represent\n // thousands/whatever separators\n normalizedString = quantityAsString\n .replaceAll('.', '_')\n .replace(',', '.');\n } else if (commaCount > 1) {\n // The second comma and everything after is \"trailing invalid\"\n if (!opts.allowTrailingInvalid) {\n // Bail out if trailing invalid is not allowed\n return NaN;\n }\n\n const firstCommaIndex = quantityAsString.indexOf(',');\n const secondCommaIndex = quantityAsString.indexOf(\n ',',\n firstCommaIndex + 1\n );\n const beforeSecondComma = quantityAsString\n .substring(0, secondCommaIndex)\n .replaceAll('.', '_')\n .replace(',', '.');\n const afterSecondComma = quantityAsString.substring(secondCommaIndex + 1);\n normalizedString = opts.allowTrailingInvalid\n ? beforeSecondComma + '&' + afterSecondComma\n : beforeSecondComma;\n } else {\n // No comma as decimal separator, so remove all \".\" since they represent\n // thousands/whatever separators\n normalizedString = quantityAsString.replaceAll('.', '_');\n }\n }\n\n const regexResult = (\n opts.allowTrailingInvalid ? numericRegexWithTrailingInvalid : numericRegex\n ).exec(normalizedString);\n\n // If the Arabic numeral regex fails, try Roman numerals\n if (!regexResult) {\n return opts.romanNumerals ? parseRomanNumerals(quantityAsString) : NaN;\n }\n\n const [, dash, ng1temp, ng2temp] = regexResult;\n const numberGroup1 = ng1temp.replaceAll(',', '').replaceAll('_', '');\n const numberGroup2 = ng2temp?.replaceAll(',', '').replaceAll('_', '');\n\n // Numerify capture group 1\n if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith('.')) {\n finalResult = 0;\n } else {\n if (opts.bigIntOnOverflow) {\n const asBigInt = dash ? BigInt(`-${numberGroup1}`) : BigInt(numberGroup1);\n if (\n asBigInt > BigInt(Number.MAX_SAFE_INTEGER) ||\n asBigInt < BigInt(Number.MIN_SAFE_INTEGER)\n ) {\n return asBigInt;\n }\n }\n\n finalResult = parseInt(numberGroup1);\n }\n\n // If capture group 2 is null, then we're dealing with an integer\n // and there is nothing left to process\n if (!numberGroup2) {\n return dash ? finalResult * -1 : finalResult;\n }\n\n const roundingFactor =\n opts.round === false\n ? NaN\n : parseFloat(`1e${Math.floor(Math.max(0, opts.round))}`);\n\n if (\n numberGroup2.startsWith('.') ||\n numberGroup2.startsWith('e') ||\n numberGroup2.startsWith('E')\n ) {\n // If first char of `numberGroup2` is \".\" or \"e\"/\"E\", it's a decimal\n const decimalValue = parseFloat(`${finalResult}${numberGroup2}`);\n finalResult = isNaN(roundingFactor)\n ? decimalValue\n : Math.round(decimalValue * roundingFactor) / roundingFactor;\n } else if (spaceThenSlashRegex.test(numberGroup2)) {\n // If the first non-space char is \"/\" it's a pure fraction (e.g. \"1/2\")\n const numerator = parseInt(numberGroup1);\n const denominator = parseInt(numberGroup2.replace('/', ''));\n finalResult = isNaN(roundingFactor)\n ? numerator / denominator\n : Math.round((numerator * roundingFactor) / denominator) / roundingFactor;\n } else {\n // Otherwise it's a mixed fraction (e.g. \"1 2/3\")\n const fractionArray = numberGroup2.split('/');\n const [numerator, denominator] = fractionArray.map(v => parseInt(v));\n finalResult += isNaN(roundingFactor)\n ? numerator / denominator\n : Math.round((numerator * roundingFactor) / denominator) / roundingFactor;\n }\n\n return dash ? finalResult * -1 : finalResult;\n}\n\nexport { numericQuantity };\n"],"mappings":";;;;;;;AAeA,MAAM,0BAA0B;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;AASD,MAAa,mBAAmB,QAC9B,IAAI,wBAAQ,2BAAU,GAAE,OAAM;CAC5B,MAAM,KAAK,GAAG,YAAY,EAAE;AAE5B,KAAI,MAAM,GAAM,QAAO;CAEvB,IAAI,KAAK;CACT,IAAI,KAAK,wBAAwB,SAAS;AAC1C,QAAO,KAAK,IAAI;EACd,MAAM,MAAO,KAAK,KAAK,MAAO;AAC9B,MAAI,wBAAwB,QAAQ,GAClC,MAAK;MAEL,MAAK,MAAM;;AAGf,QAAO,OAAO,KAAK,wBAAwB,IAAK;EAChD;;;;AAKJ,MAAa,2BAGT;CACF,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACN;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BD,MAAa,eACX;;;;AAIF,MAAa,kCACX;;;;AAKF,MAAa,uBAA+B;;;;AAe5C,MAAa,qBAET;CACF,KAAK;CACL,IAAI;CACJ,GAAG;CACH,IAAI;CACJ,MAAM;CACN,KAAK;CACL,IAAI;CACJ,GAAG;CACH,IAAI;CACJ,KAAK;CACL,IAAI;CACJ,GAAG;CACH,IAAI;CACJ,MAAM;CACN,KAAK;CACL,IAAI;CACJ,GAAG;CACH,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"}
|
|
@@ -1,5 +1,109 @@
|
|
|
1
1
|
//#region src/constants.ts
|
|
2
2
|
/**
|
|
3
|
+
* Unicode decimal digit block start code points.
|
|
4
|
+
* Each block contains 10 contiguous digits (0-9).
|
|
5
|
+
* This list covers all \p{Nd} (Decimal_Number) blocks through Unicode 17.0.
|
|
6
|
+
* The drift test in index.test.ts validates completeness against the JS engine.
|
|
7
|
+
*/
|
|
8
|
+
const decimalDigitBlockStarts = [
|
|
9
|
+
48,
|
|
10
|
+
1632,
|
|
11
|
+
1776,
|
|
12
|
+
1984,
|
|
13
|
+
2406,
|
|
14
|
+
2534,
|
|
15
|
+
2662,
|
|
16
|
+
2790,
|
|
17
|
+
2918,
|
|
18
|
+
3046,
|
|
19
|
+
3174,
|
|
20
|
+
3302,
|
|
21
|
+
3430,
|
|
22
|
+
3558,
|
|
23
|
+
3664,
|
|
24
|
+
3792,
|
|
25
|
+
3872,
|
|
26
|
+
4160,
|
|
27
|
+
4240,
|
|
28
|
+
6112,
|
|
29
|
+
6160,
|
|
30
|
+
6470,
|
|
31
|
+
6608,
|
|
32
|
+
6784,
|
|
33
|
+
6800,
|
|
34
|
+
6992,
|
|
35
|
+
7088,
|
|
36
|
+
7232,
|
|
37
|
+
7248,
|
|
38
|
+
42528,
|
|
39
|
+
43216,
|
|
40
|
+
43264,
|
|
41
|
+
43472,
|
|
42
|
+
43504,
|
|
43
|
+
43600,
|
|
44
|
+
44016,
|
|
45
|
+
65296,
|
|
46
|
+
66720,
|
|
47
|
+
68912,
|
|
48
|
+
68928,
|
|
49
|
+
69734,
|
|
50
|
+
69872,
|
|
51
|
+
69942,
|
|
52
|
+
70096,
|
|
53
|
+
70384,
|
|
54
|
+
70736,
|
|
55
|
+
70864,
|
|
56
|
+
71248,
|
|
57
|
+
71360,
|
|
58
|
+
71376,
|
|
59
|
+
71386,
|
|
60
|
+
71472,
|
|
61
|
+
71904,
|
|
62
|
+
72016,
|
|
63
|
+
72688,
|
|
64
|
+
72784,
|
|
65
|
+
73040,
|
|
66
|
+
73120,
|
|
67
|
+
73184,
|
|
68
|
+
73552,
|
|
69
|
+
90416,
|
|
70
|
+
92768,
|
|
71
|
+
92864,
|
|
72
|
+
93008,
|
|
73
|
+
93552,
|
|
74
|
+
118e3,
|
|
75
|
+
120782,
|
|
76
|
+
120792,
|
|
77
|
+
120802,
|
|
78
|
+
120812,
|
|
79
|
+
120822,
|
|
80
|
+
123200,
|
|
81
|
+
123632,
|
|
82
|
+
124144,
|
|
83
|
+
124401,
|
|
84
|
+
125264,
|
|
85
|
+
130032
|
|
86
|
+
];
|
|
87
|
+
/**
|
|
88
|
+
* Normalizes non-ASCII decimal digits to ASCII digits.
|
|
89
|
+
* Converts characters from Unicode decimal digit blocks (e.g., Arabic-Indic,
|
|
90
|
+
* Devanagari, Bengali) to their ASCII equivalents (0-9).
|
|
91
|
+
*
|
|
92
|
+
* All current Unicode \p{Nd} blocks are included in decimalDigitBlockStarts.
|
|
93
|
+
*/
|
|
94
|
+
const normalizeDigits = (str) => str.replace(/\p{Nd}/gu, (ch) => {
|
|
95
|
+
const cp = ch.codePointAt(0);
|
|
96
|
+
if (cp <= 57) return ch;
|
|
97
|
+
let lo = 0;
|
|
98
|
+
let hi = decimalDigitBlockStarts.length - 1;
|
|
99
|
+
while (lo < hi) {
|
|
100
|
+
const mid = lo + hi + 1 >>> 1;
|
|
101
|
+
if (decimalDigitBlockStarts[mid] <= cp) lo = mid;
|
|
102
|
+
else hi = mid - 1;
|
|
103
|
+
}
|
|
104
|
+
return String(cp - decimalDigitBlockStarts[lo]);
|
|
105
|
+
});
|
|
106
|
+
/**
|
|
3
107
|
* Map of Unicode fraction code points to their ASCII equivalents.
|
|
4
108
|
*/
|
|
5
109
|
const vulgarFractionToAsciiMap = {
|
|
@@ -191,7 +295,7 @@ const spaceThenSlashRegex = /^\s*\//;
|
|
|
191
295
|
function numericQuantity(quantity, options = defaultOptions) {
|
|
192
296
|
if (typeof quantity === "number" || typeof quantity === "bigint") return quantity;
|
|
193
297
|
let finalResult = NaN;
|
|
194
|
-
const quantityAsString = `${quantity}`.replace(vulgarFractionsRegex, (_m, vf) => ` ${vulgarFractionToAsciiMap[vf]}`).replace("⁄", "/").trim();
|
|
298
|
+
const quantityAsString = normalizeDigits(`${quantity}`.replace(vulgarFractionsRegex, (_m, vf) => ` ${vulgarFractionToAsciiMap[vf]}`).replace("⁄", "/").trim());
|
|
195
299
|
if (quantityAsString.length === 0) return NaN;
|
|
196
300
|
const opts = {
|
|
197
301
|
...defaultOptions,
|
|
@@ -233,13 +337,12 @@ function numericQuantity(quantity, options = defaultOptions) {
|
|
|
233
337
|
const denominator = parseInt(numberGroup2.replace("/", ""));
|
|
234
338
|
finalResult = isNaN(roundingFactor) ? numerator / denominator : Math.round(numerator * roundingFactor / denominator) / roundingFactor;
|
|
235
339
|
} else {
|
|
236
|
-
const
|
|
237
|
-
const [numerator, denominator] = fractionArray.map((v) => parseInt(v));
|
|
340
|
+
const [numerator, denominator] = numberGroup2.split("/").map((v) => parseInt(v));
|
|
238
341
|
finalResult += isNaN(roundingFactor) ? numerator / denominator : Math.round(numerator * roundingFactor / denominator) / roundingFactor;
|
|
239
342
|
}
|
|
240
343
|
return dash ? finalResult * -1 : finalResult;
|
|
241
344
|
}
|
|
242
345
|
|
|
243
346
|
//#endregion
|
|
244
|
-
export { defaultOptions, numericQuantity, numericRegex, numericRegexWithTrailingInvalid, parseRomanNumerals, romanNumeralRegex, romanNumeralUnicodeRegex, romanNumeralUnicodeToAsciiMap, romanNumeralValues, vulgarFractionToAsciiMap, vulgarFractionsRegex };
|
|
347
|
+
export { defaultOptions, normalizeDigits, numericQuantity, numericRegex, numericRegexWithTrailingInvalid, parseRomanNumerals, romanNumeralRegex, romanNumeralUnicodeRegex, romanNumeralUnicodeToAsciiMap, romanNumeralValues, vulgarFractionToAsciiMap, vulgarFractionsRegex };
|
|
245
348
|
//# sourceMappingURL=numeric-quantity.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"numeric-quantity.mjs","names":["vulgarFractionToAsciiMap: Record<\n VulgarFraction,\n `${number}/${number | ''}`\n>","numericRegex: RegExp","numericRegexWithTrailingInvalid: RegExp","vulgarFractionsRegex: RegExp","romanNumeralValues: {\n [k in RomanNumeralSequenceFragment]?: number;\n}","romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n>","romanNumeralUnicodeRegex: RegExp","romanNumeralRegex: RegExp","defaultOptions: Required<NumericQuantityOptions>","opts: Required<NumericQuantityOptions>"],"sources":["../src/constants.ts","../src/parseRomanNumerals.ts","../src/numericQuantity.ts"],"sourcesContent":["import type {\n NumericQuantityOptions,\n RomanNumeralAscii,\n RomanNumeralUnicode,\n VulgarFraction,\n} from './types';\n\n// #region Arabic numerals\n/**\n * Map of Unicode fraction code points to their ASCII equivalents.\n */\nexport const vulgarFractionToAsciiMap: Record<\n VulgarFraction,\n `${number}/${number | ''}`\n> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '⅟': '1/',\n} as const;\n\n/**\n * Captures the individual elements of a numeric string. Commas and underscores are allowed\n * as separators, as long as they appear between digits and are not consecutive.\n *\n * Capture groups:\n *\n * | # | Description | Example(s) |\n * | --- | ------------------------------------------------ | ------------------------------------------------------------------- |\n * | `0` | entire string | `\"2 1/3\"` from `\"2 1/3\"` |\n * | `1` | \"negative\" dash | `\"-\"` from `\"-2 1/3\"` |\n * | `2` | whole number or numerator | `\"2\"` from `\"2 1/3\"`; `\"1\"` from `\"1/3\"` |\n * | `3` | entire fraction, decimal portion, or denominator | `\" 1/3\"` from `\"2 1/3\"`; `\".33\"` from `\"2.33\"`; `\"/3\"` from `\"1/3\"` |\n *\n * _Capture group 2 may include comma/underscore separators._\n *\n * @example\n *\n * ```ts\n * numericRegex.exec(\"1\") // [ \"1\", \"1\", null, null ]\n * numericRegex.exec(\"1.23\") // [ \"1.23\", \"1\", \".23\", null ]\n * numericRegex.exec(\"1 2/3\") // [ \"1 2/3\", \"1\", \" 2/3\", \" 2\" ]\n * numericRegex.exec(\"2/3\") // [ \"2/3\", \"2\", \"/3\", null ]\n * numericRegex.exec(\"2 / 3\") // [ \"2 / 3\", \"2\", \"/ 3\", null ]\n * ```\n */\nexport const numericRegex: RegExp =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[,_]\\d|\\d)*)*)(([eE][+-]?\\d(?:[,_]\\d|\\d)*)?|\\.\\d(?:[,_]\\d|\\d)*([eE][+-]?\\d(?:[,_]\\d|\\d)*)?|(\\s+\\d(?:[,_]\\d|\\d)*\\s*)?\\s*\\/\\s*\\d(?:[,_]\\d|\\d)*)?$/;\n/**\n * Same as {@link numericRegex}, but allows (and ignores) trailing invalid characters.\n */\nexport const numericRegexWithTrailingInvalid: RegExp =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[,_]\\d|\\d)*)*)(([eE][+-]?\\d(?:[,_]\\d|\\d)*)?|\\.\\d(?:[,_]\\d|\\d)*([eE][+-]?\\d(?:[,_]\\d|\\d)*)?|(\\s+\\d(?:[,_]\\d|\\d)*\\s*)?\\s*\\/\\s*\\d(?:[,_]\\d|\\d)*)?(?:\\s*[^.\\d/].*)?/;\n\n/**\n * Captures any Unicode vulgar fractions.\n */\nexport const vulgarFractionsRegex: RegExp = /([¼½¾⅐⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞⅟}])/g;\n// #endregion\n\n// #region Roman numerals\ntype RomanNumeralSequenceFragment =\n | `${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;\n\n/**\n * Map of Roman numeral sequences to their decimal equivalents.\n */\nexport const romanNumeralValues: {\n [k in RomanNumeralSequenceFragment]?: number;\n} = {\n MMM: 3000,\n MM: 2000,\n M: 1000,\n CM: 900,\n DCCC: 800,\n DCC: 700,\n DC: 600,\n D: 500,\n CD: 400,\n CCC: 300,\n CC: 200,\n C: 100,\n XC: 90,\n LXXX: 80,\n LXX: 70,\n LX: 60,\n L: 50,\n XL: 40,\n XXX: 30,\n XX: 20,\n XII: 12, // only here for tests; not used in practice\n XI: 11, // only here for tests; not used in practice\n X: 10,\n IX: 9,\n VIII: 8,\n VII: 7,\n VI: 6,\n V: 5,\n IV: 4,\n III: 3,\n II: 2,\n I: 1,\n} as const;\n\n/**\n * Map of Unicode Roman numeral code points to their ASCII equivalents.\n */\nexport const romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n> = {\n // Roman Numeral One (U+2160)\n Ⅰ: 'I',\n // Roman Numeral Two (U+2161)\n Ⅱ: 'II',\n // Roman Numeral Three (U+2162)\n Ⅲ: 'III',\n // Roman Numeral Four (U+2163)\n Ⅳ: 'IV',\n // Roman Numeral Five (U+2164)\n Ⅴ: 'V',\n // Roman Numeral Six (U+2165)\n Ⅵ: 'VI',\n // Roman Numeral Seven (U+2166)\n Ⅶ: 'VII',\n // Roman Numeral Eight (U+2167)\n Ⅷ: 'VIII',\n // Roman Numeral Nine (U+2168)\n Ⅸ: 'IX',\n // Roman Numeral Ten (U+2169)\n Ⅹ: 'X',\n // Roman Numeral Eleven (U+216A)\n Ⅺ: 'XI',\n // Roman Numeral Twelve (U+216B)\n Ⅻ: 'XII',\n // Roman Numeral Fifty (U+216C)\n Ⅼ: 'L',\n // Roman Numeral One Hundred (U+216D)\n Ⅽ: 'C',\n // Roman Numeral Five Hundred (U+216E)\n Ⅾ: 'D',\n // Roman Numeral One Thousand (U+216F)\n Ⅿ: 'M',\n // Small Roman Numeral One (U+2170)\n ⅰ: 'I',\n // Small Roman Numeral Two (U+2171)\n ⅱ: 'II',\n // Small Roman Numeral Three (U+2172)\n ⅲ: 'III',\n // Small Roman Numeral Four (U+2173)\n ⅳ: 'IV',\n // Small Roman Numeral Five (U+2174)\n ⅴ: 'V',\n // Small Roman Numeral Six (U+2175)\n ⅵ: 'VI',\n // Small Roman Numeral Seven (U+2176)\n ⅶ: 'VII',\n // Small Roman Numeral Eight (U+2177)\n ⅷ: 'VIII',\n // Small Roman Numeral Nine (U+2178)\n ⅸ: 'IX',\n // Small Roman Numeral Ten (U+2179)\n ⅹ: 'X',\n // Small Roman Numeral Eleven (U+217A)\n ⅺ: 'XI',\n // Small Roman Numeral Twelve (U+217B)\n ⅻ: 'XII',\n // Small Roman Numeral Fifty (U+217C)\n ⅼ: 'L',\n // Small Roman Numeral One Hundred (U+217D)\n ⅽ: 'C',\n // Small Roman Numeral Five Hundred (U+217E)\n ⅾ: 'D',\n // Small Roman Numeral One Thousand (U+217F)\n ⅿ: 'M',\n} as const;\n\n/**\n * Captures all Unicode Roman numeral code points.\n */\nexport const romanNumeralUnicodeRegex: RegExp =\n /([ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿ])/gi;\n\n/**\n * Captures a valid Roman numeral sequence.\n *\n * Capture groups:\n *\n * | # | Description | Example |\n * | --- | --------------- | ------------------------ |\n * | `0` | Entire string | \"MCCXIV\" from \"MCCXIV\" |\n * | `1` | Thousands | \"M\" from \"MCCXIV\" |\n * | `2` | Hundreds | \"CC\" from \"MCCXIV\" |\n * | `3` | Tens | \"X\" from \"MCCXIV\" |\n * | `4` | Ones | \"IV\" from \"MCCXIV\" |\n *\n * @example\n *\n * ```ts\n * romanNumeralRegex.exec(\"M\") // [ \"M\", \"M\", \"\", \"\", \"\" ]\n * romanNumeralRegex.exec(\"XII\") // [ \"XII\", \"\", \"\", \"X\", \"II\" ]\n * romanNumeralRegex.exec(\"MCCXIV\") // [ \"MCCXIV\", \"M\", \"CC\", \"X\", \"IV\" ]\n * ```\n */\nexport const romanNumeralRegex: RegExp =\n /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;\n// #endregion\n\n/**\n * Default options for {@link numericQuantity}.\n */\nexport const defaultOptions: Required<NumericQuantityOptions> = {\n round: 3,\n allowTrailingInvalid: false,\n romanNumerals: false,\n bigIntOnOverflow: false,\n decimalSeparator: '.',\n} as const;\n","import {\n romanNumeralRegex,\n romanNumeralUnicodeRegex,\n romanNumeralUnicodeToAsciiMap,\n romanNumeralValues,\n} from './constants';\n\n// Just a shorthand type alias\ntype RNV = keyof typeof romanNumeralValues;\n\n/**\n * Converts a string of Roman numerals to a number, like `parseInt`\n * for Roman numerals. Uses modern, strict rules (only 1 to 3999).\n *\n * The string can include ASCII representations of Roman numerals\n * or Unicode Roman numeral code points (`U+2160` through `U+217F`).\n */\nexport const parseRomanNumerals = (romanNumerals: string): number => {\n const normalized = `${romanNumerals}`\n // Convert Unicode Roman numerals to ASCII\n .replace(\n romanNumeralUnicodeRegex,\n (_m, rn: keyof typeof romanNumeralUnicodeToAsciiMap) =>\n romanNumeralUnicodeToAsciiMap[rn]\n )\n // Normalize to uppercase (more common for Roman numerals)\n .toUpperCase();\n\n const regexResult = romanNumeralRegex.exec(normalized);\n\n if (!regexResult) {\n return NaN;\n }\n\n const [, thousands, hundreds, tens, ones] = regexResult;\n\n return (\n (romanNumeralValues[thousands as RNV] ?? 0) +\n (romanNumeralValues[hundreds as RNV] ?? 0) +\n (romanNumeralValues[tens as RNV] ?? 0) +\n (romanNumeralValues[ones as RNV] ?? 0)\n );\n};\n","import {\n defaultOptions,\n numericRegex,\n numericRegexWithTrailingInvalid,\n vulgarFractionToAsciiMap,\n vulgarFractionsRegex,\n} from './constants';\nimport { parseRomanNumerals } from './parseRomanNumerals';\nimport type { NumericQuantityOptions } from './types';\n\nconst spaceThenSlashRegex = /^\\s*\\//;\n\n/**\n * Converts a string to a number, like an enhanced version of `parseFloat`.\n *\n * The string can include mixed numbers, vulgar fractions, or Roman numerals.\n */\nfunction numericQuantity(quantity: string | number): number;\nfunction numericQuantity(\n quantity: string | number,\n options: NumericQuantityOptions & { bigIntOnOverflow: true }\n): number | bigint;\nfunction numericQuantity(\n quantity: string | number,\n options?: NumericQuantityOptions\n): number;\nfunction numericQuantity(\n quantity: string | number,\n options: NumericQuantityOptions = defaultOptions\n) {\n if (typeof quantity === 'number' || typeof quantity === 'bigint') {\n return quantity;\n }\n\n let finalResult = NaN;\n\n // Coerce to string in case qty is a number\n const quantityAsString = `${quantity}`\n // Convert vulgar fractions to ASCII, with a leading space\n // to keep the whole number and the fraction separate\n .replace(\n vulgarFractionsRegex,\n (_m, vf: keyof typeof vulgarFractionToAsciiMap) =>\n ` ${vulgarFractionToAsciiMap[vf]}`\n )\n // Convert fraction slash to standard slash\n .replace('⁄', '/')\n .trim();\n\n // Bail out if the string was only white space\n if (quantityAsString.length === 0) {\n return NaN;\n }\n\n const opts: Required<NumericQuantityOptions> = {\n ...defaultOptions,\n ...options,\n };\n\n let normalizedString = quantityAsString;\n\n if (opts.decimalSeparator === ',') {\n const commaCount = (quantityAsString.match(/,/g) || []).length;\n if (commaCount === 1) {\n // Treat lone comma as decimal separator; remove all \".\" since they represent\n // thousands/whatever separators\n normalizedString = quantityAsString\n .replaceAll('.', '_')\n .replace(',', '.');\n } else if (commaCount > 1) {\n // The second comma and everything after is \"trailing invalid\"\n if (!opts.allowTrailingInvalid) {\n // Bail out if trailing invalid is not allowed\n return NaN;\n }\n\n const firstCommaIndex = quantityAsString.indexOf(',');\n const secondCommaIndex = quantityAsString.indexOf(\n ',',\n firstCommaIndex + 1\n );\n const beforeSecondComma = quantityAsString\n .substring(0, secondCommaIndex)\n .replaceAll('.', '_')\n .replace(',', '.');\n const afterSecondComma = quantityAsString.substring(secondCommaIndex + 1);\n normalizedString = opts.allowTrailingInvalid\n ? beforeSecondComma + '&' + afterSecondComma\n : beforeSecondComma;\n } else {\n // No comma as decimal separator, so remove all \".\" since they represent\n // thousands/whatever separators\n normalizedString = quantityAsString.replaceAll('.', '_');\n }\n }\n\n const regexResult = (\n opts.allowTrailingInvalid ? numericRegexWithTrailingInvalid : numericRegex\n ).exec(normalizedString);\n\n // If the Arabic numeral regex fails, try Roman numerals\n if (!regexResult) {\n return opts.romanNumerals ? parseRomanNumerals(quantityAsString) : NaN;\n }\n\n const [, dash, ng1temp, ng2temp] = regexResult;\n const numberGroup1 = ng1temp.replaceAll(',', '').replaceAll('_', '');\n const numberGroup2 = ng2temp?.replaceAll(',', '').replaceAll('_', '');\n\n // Numerify capture group 1\n if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith('.')) {\n finalResult = 0;\n } else {\n if (opts.bigIntOnOverflow) {\n const asBigInt = dash ? BigInt(`-${numberGroup1}`) : BigInt(numberGroup1);\n if (\n asBigInt > BigInt(Number.MAX_SAFE_INTEGER) ||\n asBigInt < BigInt(Number.MIN_SAFE_INTEGER)\n ) {\n return asBigInt;\n }\n }\n\n finalResult = parseInt(numberGroup1);\n }\n\n // If capture group 2 is null, then we're dealing with an integer\n // and there is nothing left to process\n if (!numberGroup2) {\n return dash ? finalResult * -1 : finalResult;\n }\n\n const roundingFactor =\n opts.round === false\n ? NaN\n : parseFloat(`1e${Math.floor(Math.max(0, opts.round))}`);\n\n if (\n numberGroup2.startsWith('.') ||\n numberGroup2.startsWith('e') ||\n numberGroup2.startsWith('E')\n ) {\n // If first char of `numberGroup2` is \".\" or \"e\"/\"E\", it's a decimal\n const decimalValue = parseFloat(`${finalResult}${numberGroup2}`);\n finalResult = isNaN(roundingFactor)\n ? decimalValue\n : Math.round(decimalValue * roundingFactor) / roundingFactor;\n } else if (spaceThenSlashRegex.test(numberGroup2)) {\n // If the first non-space char is \"/\" it's a pure fraction (e.g. \"1/2\")\n const numerator = parseInt(numberGroup1);\n const denominator = parseInt(numberGroup2.replace('/', ''));\n finalResult = isNaN(roundingFactor)\n ? numerator / denominator\n : Math.round((numerator * roundingFactor) / denominator) / roundingFactor;\n } else {\n // Otherwise it's a mixed fraction (e.g. \"1 2/3\")\n const fractionArray = numberGroup2.split('/');\n const [numerator, denominator] = fractionArray.map(v => parseInt(v));\n finalResult += isNaN(roundingFactor)\n ? numerator / denominator\n : Math.round((numerator * roundingFactor) / denominator) / roundingFactor;\n }\n\n return dash ? finalResult * -1 : finalResult;\n}\n\nexport { numericQuantity };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"numeric-quantity.mjs","names":[],"sources":["../src/constants.ts","../src/parseRomanNumerals.ts","../src/numericQuantity.ts"],"sourcesContent":["import type {\n NumericQuantityOptions,\n RomanNumeralAscii,\n RomanNumeralUnicode,\n VulgarFraction,\n} from './types';\n\n// #region Decimal_Number Unicode category\n\n/**\n * Unicode decimal digit block start code points.\n * Each block contains 10 contiguous digits (0-9).\n * This list covers all \\p{Nd} (Decimal_Number) blocks through Unicode 17.0.\n * The drift test in index.test.ts validates completeness against the JS engine.\n */\nconst decimalDigitBlockStarts = [\n 0x0030, // ASCII (0-9)\n 0x0660, // Arabic-Indic\n 0x06f0, // Extended Arabic-Indic (Persian/Urdu)\n 0x07c0, // NKo\n 0x0966, // Devanagari\n 0x09e6, // Bengali\n 0x0a66, // Gurmukhi\n 0x0ae6, // Gujarati\n 0x0b66, // Oriya\n 0x0be6, // Tamil\n 0x0c66, // Telugu\n 0x0ce6, // Kannada\n 0x0d66, // Malayalam\n 0x0de6, // Sinhala Lith\n 0x0e50, // Thai\n 0x0ed0, // Lao\n 0x0f20, // Tibetan\n 0x1040, // Myanmar\n 0x1090, // Myanmar Shan\n 0x17e0, // Khmer\n 0x1810, // Mongolian\n 0x1946, // Limbu\n 0x19d0, // New Tai Lue\n 0x1a80, // Tai Tham Hora\n 0x1a90, // Tai Tham Tham\n 0x1b50, // Balinese\n 0x1bb0, // Sundanese\n 0x1c40, // Lepcha\n 0x1c50, // Ol Chiki\n 0xa620, // Vai\n 0xa8d0, // Saurashtra\n 0xa900, // Kayah Li\n 0xa9d0, // Javanese\n 0xa9f0, // Myanmar Tai Laing\n 0xaa50, // Cham\n 0xabf0, // Meetei Mayek\n 0xff10, // Fullwidth\n 0x104a0, // Osmanya\n 0x10d30, // Hanifi Rohingya\n 0x10d40, // Garay\n 0x11066, // Brahmi\n 0x110f0, // Sora Sompeng\n 0x11136, // Chakma\n 0x111d0, // Sharada\n 0x112f0, // Khudawadi\n 0x11450, // Newa\n 0x114d0, // Tirhuta\n 0x11650, // Modi\n 0x116c0, // Takri\n 0x116d0, // Myanmar Pao\n 0x116da, // Myanmar Eastern Pwo Karen\n 0x11730, // Ahom\n 0x118e0, // Warang Citi\n 0x11950, // Dives Akuru\n 0x11bf0, // Sunuwar\n 0x11c50, // Bhaiksuki\n 0x11d50, // Masaram Gondi\n 0x11da0, // Gunjala Gondi\n 0x11de0, // Tolong Siki\n 0x11f50, // Kawi\n 0x16130, // Gurung Khema\n 0x16a60, // Mro\n 0x16ac0, // Tangsa\n 0x16b50, // Pahawh Hmong\n 0x16d70, // Kirat Rai\n 0x1ccf0, // Outlined Digits\n 0x1d7ce, // Mathematical Bold\n 0x1d7d8, // Mathematical Double-Struck\n 0x1d7e2, // Mathematical Sans-Serif\n 0x1d7ec, // Mathematical Sans-Serif Bold\n 0x1d7f6, // Mathematical Monospace\n 0x1e140, // Nyiakeng Puachue Hmong\n 0x1e2f0, // Wancho\n 0x1e4f0, // Nag Mundari\n 0x1e5f1, // Ol Onal\n 0x1e950, // Adlam\n 0x1fbf0, // Segmented Digits\n] as const;\n\n/**\n * Normalizes non-ASCII decimal digits to ASCII digits.\n * Converts characters from Unicode decimal digit blocks (e.g., Arabic-Indic,\n * Devanagari, Bengali) to their ASCII equivalents (0-9).\n *\n * All current Unicode \\p{Nd} blocks are included in decimalDigitBlockStarts.\n */\nexport const normalizeDigits = (str: string): string =>\n str.replace(/\\p{Nd}/gu, ch => {\n const cp = ch.codePointAt(0)!;\n // ASCII digits (0x0030-0x0039) don't need conversion\n if (cp <= 0x39) return ch;\n // Binary search for the largest block start ≤ cp\n let lo = 0;\n let hi = decimalDigitBlockStarts.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >>> 1;\n if (decimalDigitBlockStarts[mid] <= cp) {\n lo = mid;\n } else {\n hi = mid - 1;\n }\n }\n return String(cp - decimalDigitBlockStarts[lo]!);\n });\n\n/**\n * Map of Unicode fraction code points to their ASCII equivalents.\n */\nexport const vulgarFractionToAsciiMap: Record<\n VulgarFraction,\n `${number}/${number | ''}`\n> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '⅟': '1/',\n} as const;\n\n/**\n * Captures the individual elements of a numeric string. Commas and underscores are allowed\n * as separators, as long as they appear between digits and are not consecutive.\n *\n * Capture groups:\n *\n * | # | Description | Example(s) |\n * | --- | ------------------------------------------------ | ------------------------------------------------------------------- |\n * | `0` | entire string | `\"2 1/3\"` from `\"2 1/3\"` |\n * | `1` | \"negative\" dash | `\"-\"` from `\"-2 1/3\"` |\n * | `2` | whole number or numerator | `\"2\"` from `\"2 1/3\"`; `\"1\"` from `\"1/3\"` |\n * | `3` | entire fraction, decimal portion, or denominator | `\" 1/3\"` from `\"2 1/3\"`; `\".33\"` from `\"2.33\"`; `\"/3\"` from `\"1/3\"` |\n *\n * _Capture group 2 may include comma/underscore separators._\n *\n * @example\n *\n * ```ts\n * numericRegex.exec(\"1\") // [ \"1\", \"1\", null, null ]\n * numericRegex.exec(\"1.23\") // [ \"1.23\", \"1\", \".23\", null ]\n * numericRegex.exec(\"1 2/3\") // [ \"1 2/3\", \"1\", \" 2/3\", \" 2\" ]\n * numericRegex.exec(\"2/3\") // [ \"2/3\", \"2\", \"/3\", null ]\n * numericRegex.exec(\"2 / 3\") // [ \"2 / 3\", \"2\", \"/ 3\", null ]\n * ```\n */\nexport const numericRegex: RegExp =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[,_]\\d|\\d)*)*)(([eE][+-]?\\d(?:[,_]\\d|\\d)*)?|\\.\\d(?:[,_]\\d|\\d)*([eE][+-]?\\d(?:[,_]\\d|\\d)*)?|(\\s+\\d(?:[,_]\\d|\\d)*\\s*)?\\s*\\/\\s*\\d(?:[,_]\\d|\\d)*)?$/;\n/**\n * Same as {@link numericRegex}, but allows (and ignores) trailing invalid characters.\n */\nexport const numericRegexWithTrailingInvalid: RegExp =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[,_]\\d|\\d)*)*)(([eE][+-]?\\d(?:[,_]\\d|\\d)*)?|\\.\\d(?:[,_]\\d|\\d)*([eE][+-]?\\d(?:[,_]\\d|\\d)*)?|(\\s+\\d(?:[,_]\\d|\\d)*\\s*)?\\s*\\/\\s*\\d(?:[,_]\\d|\\d)*)?(?:\\s*[^.\\d/].*)?/;\n\n/**\n * Captures any Unicode vulgar fractions.\n */\nexport const vulgarFractionsRegex: RegExp = /([¼½¾⅐⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞⅟}])/g;\n\n// #endregion\n\n// #region Roman numerals\n\ntype RomanNumeralSequenceFragment =\n | `${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;\n\n/**\n * Map of Roman numeral sequences to their decimal equivalents.\n */\nexport const romanNumeralValues: {\n [k in RomanNumeralSequenceFragment]?: number;\n} = {\n MMM: 3000,\n MM: 2000,\n M: 1000,\n CM: 900,\n DCCC: 800,\n DCC: 700,\n DC: 600,\n D: 500,\n CD: 400,\n CCC: 300,\n CC: 200,\n C: 100,\n XC: 90,\n LXXX: 80,\n LXX: 70,\n LX: 60,\n L: 50,\n XL: 40,\n XXX: 30,\n XX: 20,\n XII: 12, // only here for tests; not used in practice\n XI: 11, // only here for tests; not used in practice\n X: 10,\n IX: 9,\n VIII: 8,\n VII: 7,\n VI: 6,\n V: 5,\n IV: 4,\n III: 3,\n II: 2,\n I: 1,\n} as const;\n\n/**\n * Map of Unicode Roman numeral code points to their ASCII equivalents.\n */\nexport const romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n> = {\n // Roman Numeral One (U+2160)\n Ⅰ: 'I',\n // Roman Numeral Two (U+2161)\n Ⅱ: 'II',\n // Roman Numeral Three (U+2162)\n Ⅲ: 'III',\n // Roman Numeral Four (U+2163)\n Ⅳ: 'IV',\n // Roman Numeral Five (U+2164)\n Ⅴ: 'V',\n // Roman Numeral Six (U+2165)\n Ⅵ: 'VI',\n // Roman Numeral Seven (U+2166)\n Ⅶ: 'VII',\n // Roman Numeral Eight (U+2167)\n Ⅷ: 'VIII',\n // Roman Numeral Nine (U+2168)\n Ⅸ: 'IX',\n // Roman Numeral Ten (U+2169)\n Ⅹ: 'X',\n // Roman Numeral Eleven (U+216A)\n Ⅺ: 'XI',\n // Roman Numeral Twelve (U+216B)\n Ⅻ: 'XII',\n // Roman Numeral Fifty (U+216C)\n Ⅼ: 'L',\n // Roman Numeral One Hundred (U+216D)\n Ⅽ: 'C',\n // Roman Numeral Five Hundred (U+216E)\n Ⅾ: 'D',\n // Roman Numeral One Thousand (U+216F)\n Ⅿ: 'M',\n // Small Roman Numeral One (U+2170)\n ⅰ: 'I',\n // Small Roman Numeral Two (U+2171)\n ⅱ: 'II',\n // Small Roman Numeral Three (U+2172)\n ⅲ: 'III',\n // Small Roman Numeral Four (U+2173)\n ⅳ: 'IV',\n // Small Roman Numeral Five (U+2174)\n ⅴ: 'V',\n // Small Roman Numeral Six (U+2175)\n ⅵ: 'VI',\n // Small Roman Numeral Seven (U+2176)\n ⅶ: 'VII',\n // Small Roman Numeral Eight (U+2177)\n ⅷ: 'VIII',\n // Small Roman Numeral Nine (U+2178)\n ⅸ: 'IX',\n // Small Roman Numeral Ten (U+2179)\n ⅹ: 'X',\n // Small Roman Numeral Eleven (U+217A)\n ⅺ: 'XI',\n // Small Roman Numeral Twelve (U+217B)\n ⅻ: 'XII',\n // Small Roman Numeral Fifty (U+217C)\n ⅼ: 'L',\n // Small Roman Numeral One Hundred (U+217D)\n ⅽ: 'C',\n // Small Roman Numeral Five Hundred (U+217E)\n ⅾ: 'D',\n // Small Roman Numeral One Thousand (U+217F)\n ⅿ: 'M',\n} as const;\n\n/**\n * Captures all Unicode Roman numeral code points.\n */\nexport const romanNumeralUnicodeRegex: RegExp =\n /([ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿ])/gi;\n\n/**\n * Captures a valid Roman numeral sequence.\n *\n * Capture groups:\n *\n * | # | Description | Example |\n * | --- | --------------- | ------------------------ |\n * | `0` | Entire string | \"MCCXIV\" from \"MCCXIV\" |\n * | `1` | Thousands | \"M\" from \"MCCXIV\" |\n * | `2` | Hundreds | \"CC\" from \"MCCXIV\" |\n * | `3` | Tens | \"X\" from \"MCCXIV\" |\n * | `4` | Ones | \"IV\" from \"MCCXIV\" |\n *\n * @example\n *\n * ```ts\n * romanNumeralRegex.exec(\"M\") // [ \"M\", \"M\", \"\", \"\", \"\" ]\n * romanNumeralRegex.exec(\"XII\") // [ \"XII\", \"\", \"\", \"X\", \"II\" ]\n * romanNumeralRegex.exec(\"MCCXIV\") // [ \"MCCXIV\", \"M\", \"CC\", \"X\", \"IV\" ]\n * ```\n */\nexport const romanNumeralRegex: RegExp =\n /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;\n\n// #endregion\n\n/**\n * Default options for {@link numericQuantity}.\n */\nexport const defaultOptions: Required<NumericQuantityOptions> = {\n round: 3,\n allowTrailingInvalid: false,\n romanNumerals: false,\n bigIntOnOverflow: false,\n decimalSeparator: '.',\n} as const;\n","import {\n romanNumeralRegex,\n romanNumeralUnicodeRegex,\n romanNumeralUnicodeToAsciiMap,\n romanNumeralValues,\n} from './constants';\n\n// Just a shorthand type alias\ntype RNV = keyof typeof romanNumeralValues;\n\n/**\n * Converts a string of Roman numerals to a number, like `parseInt`\n * for Roman numerals. Uses modern, strict rules (only 1 to 3999).\n *\n * The string can include ASCII representations of Roman numerals\n * or Unicode Roman numeral code points (`U+2160` through `U+217F`).\n */\nexport const parseRomanNumerals = (romanNumerals: string): number => {\n const normalized = `${romanNumerals}`\n // Convert Unicode Roman numerals to ASCII\n .replace(\n romanNumeralUnicodeRegex,\n (_m, rn: keyof typeof romanNumeralUnicodeToAsciiMap) =>\n romanNumeralUnicodeToAsciiMap[rn]\n )\n // Normalize to uppercase (more common for Roman numerals)\n .toUpperCase();\n\n const regexResult = romanNumeralRegex.exec(normalized);\n\n if (!regexResult) {\n return NaN;\n }\n\n const [, thousands, hundreds, tens, ones] = regexResult;\n\n return (\n (romanNumeralValues[thousands as RNV] ?? 0) +\n (romanNumeralValues[hundreds as RNV] ?? 0) +\n (romanNumeralValues[tens as RNV] ?? 0) +\n (romanNumeralValues[ones as RNV] ?? 0)\n );\n};\n","import {\n defaultOptions,\n normalizeDigits,\n numericRegex,\n numericRegexWithTrailingInvalid,\n vulgarFractionToAsciiMap,\n vulgarFractionsRegex,\n} from './constants';\nimport { parseRomanNumerals } from './parseRomanNumerals';\nimport type { NumericQuantityOptions } from './types';\n\nconst spaceThenSlashRegex = /^\\s*\\//;\n\n/**\n * Converts a string to a number, like an enhanced version of `parseFloat`.\n *\n * The string can include mixed numbers, vulgar fractions, or Roman numerals.\n */\nfunction numericQuantity(quantity: string | number): number;\nfunction numericQuantity(\n quantity: string | number,\n options: NumericQuantityOptions & { bigIntOnOverflow: true }\n): number | bigint;\nfunction numericQuantity(\n quantity: string | number,\n options?: NumericQuantityOptions\n): number;\nfunction numericQuantity(\n quantity: string | number,\n options: NumericQuantityOptions = defaultOptions\n) {\n if (typeof quantity === 'number' || typeof quantity === 'bigint') {\n return quantity;\n }\n\n let finalResult = NaN;\n\n // Coerce to string in case qty is a number\n const quantityAsString = normalizeDigits(\n `${quantity}`\n // Convert vulgar fractions to ASCII, with a leading space\n // to keep the whole number and the fraction separate\n .replace(\n vulgarFractionsRegex,\n (_m, vf: keyof typeof vulgarFractionToAsciiMap) =>\n ` ${vulgarFractionToAsciiMap[vf]}`\n )\n // Convert fraction slash to standard slash\n .replace('⁄', '/')\n .trim()\n );\n\n // Bail out if the string was only white space\n if (quantityAsString.length === 0) {\n return NaN;\n }\n\n const opts: Required<NumericQuantityOptions> = {\n ...defaultOptions,\n ...options,\n };\n\n let normalizedString = quantityAsString;\n\n if (opts.decimalSeparator === ',') {\n const commaCount = (quantityAsString.match(/,/g) || []).length;\n if (commaCount === 1) {\n // Treat lone comma as decimal separator; remove all \".\" since they represent\n // thousands/whatever separators\n normalizedString = quantityAsString\n .replaceAll('.', '_')\n .replace(',', '.');\n } else if (commaCount > 1) {\n // The second comma and everything after is \"trailing invalid\"\n if (!opts.allowTrailingInvalid) {\n // Bail out if trailing invalid is not allowed\n return NaN;\n }\n\n const firstCommaIndex = quantityAsString.indexOf(',');\n const secondCommaIndex = quantityAsString.indexOf(\n ',',\n firstCommaIndex + 1\n );\n const beforeSecondComma = quantityAsString\n .substring(0, secondCommaIndex)\n .replaceAll('.', '_')\n .replace(',', '.');\n const afterSecondComma = quantityAsString.substring(secondCommaIndex + 1);\n normalizedString = opts.allowTrailingInvalid\n ? beforeSecondComma + '&' + afterSecondComma\n : beforeSecondComma;\n } else {\n // No comma as decimal separator, so remove all \".\" since they represent\n // thousands/whatever separators\n normalizedString = quantityAsString.replaceAll('.', '_');\n }\n }\n\n const regexResult = (\n opts.allowTrailingInvalid ? numericRegexWithTrailingInvalid : numericRegex\n ).exec(normalizedString);\n\n // If the Arabic numeral regex fails, try Roman numerals\n if (!regexResult) {\n return opts.romanNumerals ? parseRomanNumerals(quantityAsString) : NaN;\n }\n\n const [, dash, ng1temp, ng2temp] = regexResult;\n const numberGroup1 = ng1temp.replaceAll(',', '').replaceAll('_', '');\n const numberGroup2 = ng2temp?.replaceAll(',', '').replaceAll('_', '');\n\n // Numerify capture group 1\n if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith('.')) {\n finalResult = 0;\n } else {\n if (opts.bigIntOnOverflow) {\n const asBigInt = dash ? BigInt(`-${numberGroup1}`) : BigInt(numberGroup1);\n if (\n asBigInt > BigInt(Number.MAX_SAFE_INTEGER) ||\n asBigInt < BigInt(Number.MIN_SAFE_INTEGER)\n ) {\n return asBigInt;\n }\n }\n\n finalResult = parseInt(numberGroup1);\n }\n\n // If capture group 2 is null, then we're dealing with an integer\n // and there is nothing left to process\n if (!numberGroup2) {\n return dash ? finalResult * -1 : finalResult;\n }\n\n const roundingFactor =\n opts.round === false\n ? NaN\n : parseFloat(`1e${Math.floor(Math.max(0, opts.round))}`);\n\n if (\n numberGroup2.startsWith('.') ||\n numberGroup2.startsWith('e') ||\n numberGroup2.startsWith('E')\n ) {\n // If first char of `numberGroup2` is \".\" or \"e\"/\"E\", it's a decimal\n const decimalValue = parseFloat(`${finalResult}${numberGroup2}`);\n finalResult = isNaN(roundingFactor)\n ? decimalValue\n : Math.round(decimalValue * roundingFactor) / roundingFactor;\n } else if (spaceThenSlashRegex.test(numberGroup2)) {\n // If the first non-space char is \"/\" it's a pure fraction (e.g. \"1/2\")\n const numerator = parseInt(numberGroup1);\n const denominator = parseInt(numberGroup2.replace('/', ''));\n finalResult = isNaN(roundingFactor)\n ? numerator / denominator\n : Math.round((numerator * roundingFactor) / denominator) / roundingFactor;\n } else {\n // Otherwise it's a mixed fraction (e.g. \"1 2/3\")\n const fractionArray = numberGroup2.split('/');\n const [numerator, denominator] = fractionArray.map(v => parseInt(v));\n finalResult += isNaN(roundingFactor)\n ? numerator / denominator\n : Math.round((numerator * roundingFactor) / denominator) / roundingFactor;\n }\n\n return dash ? finalResult * -1 : finalResult;\n}\n\nexport { numericQuantity };\n"],"mappings":";;;;;;;AAeA,MAAM,0BAA0B;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;AASD,MAAa,mBAAmB,QAC9B,IAAI,QAAQ,aAAY,OAAM;CAC5B,MAAM,KAAK,GAAG,YAAY,EAAE;AAE5B,KAAI,MAAM,GAAM,QAAO;CAEvB,IAAI,KAAK;CACT,IAAI,KAAK,wBAAwB,SAAS;AAC1C,QAAO,KAAK,IAAI;EACd,MAAM,MAAO,KAAK,KAAK,MAAO;AAC9B,MAAI,wBAAwB,QAAQ,GAClC,MAAK;MAEL,MAAK,MAAM;;AAGf,QAAO,OAAO,KAAK,wBAAwB,IAAK;EAChD;;;;AAKJ,MAAa,2BAGT;CACF,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACN;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BD,MAAa,eACX;;;;AAIF,MAAa,kCACX;;;;AAKF,MAAa,uBAA+B;;;;AAe5C,MAAa,qBAET;CACF,KAAK;CACL,IAAI;CACJ,GAAG;CACH,IAAI;CACJ,MAAM;CACN,KAAK;CACL,IAAI;CACJ,GAAG;CACH,IAAI;CACJ,KAAK;CACL,IAAI;CACJ,GAAG;CACH,IAAI;CACJ,MAAM;CACN,KAAK;CACL,IAAI;CACJ,GAAG;CACH,IAAI;CA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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e={"¼":`1/4`,"½":`1/2`,"¾":`3/4`,"⅐":`1/7`,"⅑":`1/9`,"⅒":`1/10`,"⅓":`1/3`,"⅔":`2/3`,"⅕":`1/5`,"⅖":`2/5`,"⅗":`3/5`,"⅘":`4/5`,"⅙":`1/6`,"⅚":`5/6`,"⅛":`1/8`,"⅜":`3/8`,"⅝":`5/8`,"⅞":`7/8`,"⅟":`1/`},
|
|
1
|
+
const e=[48,1632,1776,1984,2406,2534,2662,2790,2918,3046,3174,3302,3430,3558,3664,3792,3872,4160,4240,6112,6160,6470,6608,6784,6800,6992,7088,7232,7248,42528,43216,43264,43472,43504,43600,44016,65296,66720,68912,68928,69734,69872,69942,70096,70384,70736,70864,71248,71360,71376,71386,71472,71904,72016,72688,72784,73040,73120,73184,73552,90416,92768,92864,93008,93552,118e3,120782,120792,120802,120812,120822,123200,123632,124144,124401,125264,130032],t=t=>t.replace(/\p{Nd}/gu,t=>{let n=t.codePointAt(0);if(n<=57)return t;let r=0,i=e.length-1;for(;r<i;){let t=r+i+1>>>1;e[t]<=n?r=t:i=t-1}return String(n-e[r])}),n={"¼":`1/4`,"½":`1/2`,"¾":`3/4`,"⅐":`1/7`,"⅑":`1/9`,"⅒":`1/10`,"⅓":`1/3`,"⅔":`2/3`,"⅕":`1/5`,"⅖":`2/5`,"⅗":`3/5`,"⅘":`4/5`,"⅙":`1/6`,"⅚":`5/6`,"⅛":`1/8`,"⅜":`3/8`,"⅝":`5/8`,"⅞":`7/8`,"⅟":`1/`},r=/^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[,_]\d|\d)*)*)(([eE][+-]?\d(?:[,_]\d|\d)*)?|\.\d(?:[,_]\d|\d)*([eE][+-]?\d(?:[,_]\d|\d)*)?|(\s+\d(?:[,_]\d|\d)*\s*)?\s*\/\s*\d(?:[,_]\d|\d)*)?$/,i=/^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[,_]\d|\d)*)*)(([eE][+-]?\d(?:[,_]\d|\d)*)?|\.\d(?:[,_]\d|\d)*([eE][+-]?\d(?:[,_]\d|\d)*)?|(\s+\d(?:[,_]\d|\d)*\s*)?\s*\/\s*\d(?:[,_]\d|\d)*)?(?:\s*[^.\d/].*)?/,a=/([¼½¾⅐⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞⅟}])/g,o={MMM:3e3,MM:2e3,M:1e3,CM:900,DCCC:800,DCC:700,DC:600,D:500,CD:400,CCC:300,CC:200,C:100,XC:90,LXXX:80,LXX:70,LX:60,L:50,XL:40,XXX:30,XX:20,XII:12,XI:11,X:10,IX:9,VIII:8,VII:7,VI:6,V:5,IV:4,III:3,II:2,I:1},s={Ⅰ:`I`,Ⅱ:`II`,Ⅲ:`III`,Ⅳ:`IV`,Ⅴ:`V`,Ⅵ:`VI`,Ⅶ:`VII`,Ⅷ:`VIII`,Ⅸ:`IX`,Ⅹ:`X`,Ⅺ:`XI`,Ⅻ:`XII`,Ⅼ:`L`,Ⅽ:`C`,Ⅾ:`D`,Ⅿ:`M`,ⅰ:`I`,ⅱ:`II`,ⅲ:`III`,ⅳ:`IV`,ⅴ:`V`,ⅵ:`VI`,ⅶ:`VII`,ⅷ:`VIII`,ⅸ:`IX`,ⅹ:`X`,ⅺ:`XI`,ⅻ:`XII`,ⅼ:`L`,ⅽ:`C`,ⅾ:`D`,ⅿ:`M`},c=/([ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿ])/gi,l=/^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i,u={round:3,allowTrailingInvalid:!1,romanNumerals:!1,bigIntOnOverflow:!1,decimalSeparator:`.`},d=e=>{let t=`${e}`.replace(c,(e,t)=>s[t]).toUpperCase(),n=l.exec(t);if(!n)return NaN;let[,r,i,a,u]=n;return(o[r]??0)+(o[i]??0)+(o[a]??0)+(o[u]??0)},f=/^\s*\//;function p(e,o=u){if(typeof e==`number`||typeof e==`bigint`)return e;let s=NaN,c=t(`${e}`.replace(a,(e,t)=>` ${n[t]}`).replace(`⁄`,`/`).trim());if(c.length===0)return NaN;let l={...u,...o},p=c;if(l.decimalSeparator===`,`){let e=(c.match(/,/g)||[]).length;if(e===1)p=c.replaceAll(`.`,`_`).replace(`,`,`.`);else if(e>1){if(!l.allowTrailingInvalid)return NaN;let e=c.indexOf(`,`),t=c.indexOf(`,`,e+1),n=c.substring(0,t).replaceAll(`.`,`_`).replace(`,`,`.`),r=c.substring(t+1);p=l.allowTrailingInvalid?n+`&`+r:n}else p=c.replaceAll(`.`,`_`)}let m=(l.allowTrailingInvalid?i:r).exec(p);if(!m)return l.romanNumerals?d(c):NaN;let[,h,g,_]=m,v=g.replaceAll(`,`,``).replaceAll(`_`,``),y=_==null?void 0:_.replaceAll(`,`,``).replaceAll(`_`,``);if(!v&&y&&y.startsWith(`.`))s=0;else{if(l.bigIntOnOverflow){let e=h?BigInt(`-${v}`):BigInt(v);if(e>BigInt(2**53-1)||e<BigInt(-(2**53-1)))return e}s=parseInt(v)}if(!y)return h?s*-1:s;let b=l.round===!1?NaN:parseFloat(`1e${Math.floor(Math.max(0,l.round))}`);if(y.startsWith(`.`)||y.startsWith(`e`)||y.startsWith(`E`)){let e=parseFloat(`${s}${y}`);s=isNaN(b)?e:Math.round(e*b)/b}else if(f.test(y)){let e=parseInt(v),t=parseInt(y.replace(`/`,``));s=isNaN(b)?e/t:Math.round(e*b/t)/b}else{let[e,t]=y.split(`/`).map(e=>parseInt(e));s+=isNaN(b)?e/t:Math.round(e*b/t)/b}return h?s*-1:s}export{u as defaultOptions,t as normalizeDigits,p as numericQuantity,r as numericRegex,i as numericRegexWithTrailingInvalid,d as parseRomanNumerals,l as romanNumeralRegex,c as romanNumeralUnicodeRegex,s as romanNumeralUnicodeToAsciiMap,o as romanNumeralValues,n as vulgarFractionToAsciiMap,a as vulgarFractionsRegex};
|
|
2
2
|
//# sourceMappingURL=numeric-quantity.production.mjs.map
|