numeric-quantity 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +173 -17
  2. package/dist/cjs/index.d.ts +1 -0
  3. package/dist/cjs/numeric-quantity.cjs.development.d.ts +225 -0
  4. package/dist/cjs/numeric-quantity.cjs.development.js +101 -16
  5. package/dist/cjs/numeric-quantity.cjs.development.js.map +1 -1
  6. package/dist/cjs/numeric-quantity.cjs.production.d.ts +225 -0
  7. package/dist/cjs/numeric-quantity.cjs.production.js +1 -1
  8. package/dist/cjs/numeric-quantity.cjs.production.js.map +1 -1
  9. package/dist/numeric-quantity.d.mts +225 -0
  10. package/dist/numeric-quantity.iife.umd.min.js +1 -1
  11. package/dist/numeric-quantity.iife.umd.min.js.map +1 -1
  12. package/dist/numeric-quantity.legacy-esm.d.ts +225 -0
  13. package/dist/numeric-quantity.legacy-esm.js +98 -17
  14. package/dist/numeric-quantity.legacy-esm.js.map +1 -1
  15. package/dist/numeric-quantity.mjs +101 -17
  16. package/dist/numeric-quantity.mjs.map +1 -1
  17. package/dist/numeric-quantity.production.d.mts +225 -0
  18. package/dist/numeric-quantity.production.mjs +1 -1
  19. package/dist/numeric-quantity.production.mjs.map +1 -1
  20. package/package.json +5 -6
  21. package/dist/types/constants.d.ts +0 -87
  22. package/dist/types/dev.d.ts +0 -1
  23. package/dist/types/index.d.ts +0 -4
  24. package/dist/types/numericQuantity.d.ts +0 -12
  25. package/dist/types/parseRomanNumerals.d.ts +0 -8
  26. package/dist/types/types.d.ts +0 -49
  27. package/dist/types-esm/constants.d.mts +0 -87
  28. package/dist/types-esm/dev.d.mts +0 -1
  29. package/dist/types-esm/index.d.mts +0 -4
  30. package/dist/types-esm/numericQuantity.d.mts +0 -12
  31. package/dist/types-esm/parseRomanNumerals.d.mts +0 -8
  32. package/dist/types-esm/types.d.mts +0 -49
package/README.md CHANGED
@@ -4,20 +4,16 @@
4
4
  [![downloads](https://img.shields.io/npm/dm/numeric-quantity.svg)](https://npm-stat.com/charts.html?package=numeric-quantity&from=2015-08-01)
5
5
  [![MIT License](https://img.shields.io/npm/l/numeric-quantity.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
- Converts a string to a number, like an enhanced version of `parseFloat`.
7
+ Converts a string to a number, like an enhanced version of `parseFloat`. Returns `NaN` if the provided string does not resemble a number.
8
8
 
9
9
  **[Full documentation](https://jakeboone02.github.io/numeric-quantity/)**
10
10
 
11
- Features:
11
+ In addition to plain integers and decimals, `numeric-quantity` handles:
12
12
 
13
- - In addition to plain integers and decimals, `numeric-quantity` can parse numbers with comma or underscore separators (`'1,000'` or `'1_000'`), mixed numbers (`'1 2/3'`), vulgar fractions (`'1⅖'`), and the fraction slash character (`'1 2⁄3'`).
14
- - Supports non-ASCII decimal numeral systems including Arabic-Indic (`'٣'`), Devanagari (`'३'`), Bengali (`'৩'`), Thai (`''`), Fullwidth (`'3'`), and 70+ other Unicode digit scripts.
15
- - To allow and ignore trailing invalid characters _à la_ `parseFloat`, pass `{ allowTrailingInvalid: true }` as the second argument.
16
- - To parse Roman numerals like `'MCCXIV'` or `''`, pass `{ romanNumerals: true }` as the second argument or call `parseRomanNumerals` directly.
17
- - To parse numbers with European-style decimal comma (where `'1,0'` means `1`, not `10`), pass `{ decimalSeparator: ',' }` as the second argument.
18
- - To produce `bigint` values when the input represents an integer that would exceeds the boundaries of `number`, pass `{ bigIntOnOverflow: true }` as the second argument.
19
- - Results will be rounded to three decimal places by default. To avoid rounding, pass `{ round: false }` as the second argument. To round to a different number of decimal places, assign that number to the `round` option (`{ round: 5 }` will round to five decimal places).
20
- - Returns `NaN` if the provided string does not resemble a number.
13
+ - **Fractions and mixed numbers**: `'1 2/3'` `1.667`, `'1⅖'` `1.4`, `'1 2⁄3'` → `1.667`
14
+ - **Separators**: `'1,000'` `1000`, `'1_000_000'` `1000000`
15
+ - **Roman numerals** (see [option](#roman-numerals-romannumerals) below): `'XIV'` `14`, `'Ⅻ'` `12`
16
+ - **Non-ASCII numerals**: Arabic-Indic (`'٣'`), Devanagari (`''`), Bengali, Thai, Fullwidth, and 70+ other Unicode digit scripts
21
17
 
22
18
  > _For the inverse operation—converting a number to an imperial measurement—check out [format-quantity](https://www.npmjs.com/package/format-quantity)._
23
19
 
@@ -57,12 +53,172 @@ As UMD (all exports are properties of the global object `NumericQuantity`):
57
53
 
58
54
  ## Options
59
55
 
60
- | Option | Type | Default | Description |
61
- | ---------------------- | ----------------- | ------- | ---------------------------------------------------------------------------------------------------- |
62
- | `round` | `number \| false` | `3` | Round the result to a certain number of decimal places. Must be greater than or equal to zero. |
63
- | `allowTrailingInvalid` | `boolean` | `false` | Allow and ignore trailing invalid characters _à la_ `parseFloat`. |
64
- | `romanNumerals` | `boolean` | `false` | Attempt to parse Roman numerals if Arabic numeral parsing fails. |
65
- | `bigIntOnOverflow` | `boolean` | `false` | Generates a `bigint` value if the string represents a valid integer too large for the `number` type. |
66
- | `decimalSeparator` | `',' \| '.'` | `"."` | Specifies which character to treat as the decimal separator. |
56
+ All options are passed as the second argument to `numericQuantity` (and `isNumericQuantity`).
57
+
58
+ ### Rounding (`round`)
59
+
60
+ Results are rounded to three decimal places by default. Use the `round` option to change this behavior.
61
+
62
+ ```js
63
+ numericQuantity('1/3'); // 0.333 (default: 3 decimal places)
64
+ numericQuantity('1/3', { round: 5 }); // 0.33333
65
+ numericQuantity('1/3', { round: false }); // 0.3333333333333333
66
+ ```
67
+
68
+ ### Trailing Invalid Characters (`allowTrailingInvalid`)
69
+
70
+ By default, strings with trailing non-numeric characters return `NaN`. Set `allowTrailingInvalid: true` to ignore trailing invalid characters, similar to `parseFloat`.
71
+
72
+ ```js
73
+ numericQuantity('100abc'); // NaN
74
+ numericQuantity('100abc', { allowTrailingInvalid: true }); // 100
75
+ ```
76
+
77
+ ### Roman Numerals (`romanNumerals`)
78
+
79
+ Parse Roman numerals (ASCII or Unicode) by setting `romanNumerals: true`. You can also use `parseRomanNumerals` directly.
80
+
81
+ ```js
82
+ numericQuantity('MCCXIV', { romanNumerals: true }); // 1214
83
+ numericQuantity('Ⅻ', { romanNumerals: true }); // 12
84
+ numericQuantity('xiv', { romanNumerals: true }); // 14 (case-insensitive)
85
+ ```
86
+
87
+ ### Decimal Separator (`decimalSeparator`)
88
+
89
+ For European-style numbers where comma is the decimal separator, set `decimalSeparator: ','`.
90
+
91
+ ```js
92
+ numericQuantity('1,5'); // 15 (comma treated as thousands separator)
93
+ numericQuantity('1,5', { decimalSeparator: ',' }); // 1.5
94
+ numericQuantity('1.000,50', { decimalSeparator: ',' }); // 1000.5
95
+ ```
96
+
97
+ ### Large Integers (`bigIntOnOverflow`)
98
+
99
+ When parsing integers that exceed `Number.MAX_SAFE_INTEGER` or are less than `Number.MIN_SAFE_INTEGER`, set `bigIntOnOverflow: true` to return a `bigint` instead.
100
+
101
+ ```js
102
+ numericQuantity('9007199254740992'); // 9007199254740992 (loses precision)
103
+ numericQuantity('9007199254740992', { bigIntOnOverflow: true }); // 9007199254740992n
104
+ ```
105
+
106
+ ### Percentages (`percentage`)
107
+
108
+ Parse percentage strings by setting the `percentage` option. Use `'decimal'` (or `true`) to divide by 100, or `'number'` to just strip the `%` symbol.
109
+
110
+ ```js
111
+ numericQuantity('50%'); // NaN
112
+ numericQuantity('50%', { percentage: true }); // 0.5
113
+ numericQuantity('50%', { percentage: 'decimal' }); // 0.5
114
+ numericQuantity('50%', { percentage: 'number' }); // 50
115
+ numericQuantity('1/2%', { percentage: true }); // 0.005
116
+ ```
117
+
118
+ ### Currency Symbols (`allowCurrency`)
119
+
120
+ Strip currency symbols from the start or end of the string by setting `allowCurrency: true`. Supports all Unicode currency symbols (`$`, `€`, `£`, `¥`, `₹`, `₽`, `₿`, `₩`, etc.).
121
+
122
+ ```js
123
+ numericQuantity('$100'); // NaN
124
+ numericQuantity('$100', { allowCurrency: true }); // 100
125
+ numericQuantity('€1.000,50', { allowCurrency: true, decimalSeparator: ',' }); // 1000.5
126
+ numericQuantity('100€', { allowCurrency: true }); // 100
127
+ numericQuantity('-$50', { allowCurrency: true }); // -50
128
+ ```
129
+
130
+ ### Verbose Output (`verbose`)
131
+
132
+ Set `verbose: true` to return a detailed result object instead of just the numeric value. This is useful for understanding what was parsed and stripped.
133
+
134
+ ```js
135
+ numericQuantity('$50%', {
136
+ verbose: true,
137
+ allowCurrency: true,
138
+ percentage: true,
139
+ });
140
+ // {
141
+ // value: 0.5,
142
+ // input: '$50%',
143
+ // currencyPrefix: '$',
144
+ // percentageSuffix: true
145
+ // }
146
+
147
+ numericQuantity('100abc', {
148
+ verbose: true,
149
+ allowTrailingInvalid: true,
150
+ });
151
+ // {
152
+ // value: 100,
153
+ // input: '100abc',
154
+ // trailingInvalid: 'abc'
155
+ // }
156
+ ```
157
+
158
+ For fraction and mixed-number inputs, the result also includes parsed fraction components (always unsigned):
159
+
160
+ ```js
161
+ numericQuantity('1 2/3', { verbose: true });
162
+ // {
163
+ // value: 1.667,
164
+ // input: '1 2/3',
165
+ // whole: 1,
166
+ // numerator: 2,
167
+ // denominator: 3
168
+ // }
169
+
170
+ numericQuantity('½', { verbose: true });
171
+ // {
172
+ // value: 0.5,
173
+ // input: '½',
174
+ // numerator: 1,
175
+ // denominator: 2
176
+ // }
177
+ ```
178
+
179
+ The verbose result object has the following shape:
180
+
181
+ ```ts
182
+ interface NumericQuantityVerboseResult {
183
+ value: number | bigint; // The parsed value (NaN if invalid)
184
+ input: string; // Original input string
185
+ currencyPrefix?: string; // Currency symbol(s) stripped from start
186
+ currencySuffix?: string; // Currency symbol(s) stripped from end
187
+ percentageSuffix?: boolean; // True if "%" was stripped
188
+ trailingInvalid?: string; // Characters ignored (if allowTrailingInvalid)
189
+ sign?: '-' | '+'; // Leading sign character, if present
190
+ whole?: number; // Whole part of a mixed fraction (e.g. 1 from "1 2/3")
191
+ numerator?: number; // Fraction numerator (e.g. 2 from "1 2/3")
192
+ denominator?: number; // Fraction denominator (e.g. 3 from "1 2/3")
193
+ }
194
+ ```
195
+
196
+ ## Additional Exports
197
+
198
+ ### `isNumericQuantity(str, options?): boolean`
199
+
200
+ Returns `true` if the string can be parsed as a valid number, `false` otherwise. Accepts the same options as `numericQuantity`.
201
+
202
+ ```js
203
+ import { isNumericQuantity } from 'numeric-quantity';
204
+
205
+ isNumericQuantity('1 1/2'); // true
206
+ isNumericQuantity('abc'); // false
207
+ isNumericQuantity('XII', { romanNumerals: true }); // true
208
+ isNumericQuantity('$100', { allowCurrency: true }); // true
209
+ isNumericQuantity('50%', { percentage: true }); // true
210
+ ```
211
+
212
+ ### `parseRomanNumerals(str): number`
213
+
214
+ Parses a string of Roman numerals directly. Returns `NaN` for invalid input.
215
+
216
+ ```js
217
+ import { parseRomanNumerals } from 'numeric-quantity';
218
+
219
+ parseRomanNumerals('MCMXCIX'); // 1999
220
+ parseRomanNumerals('Ⅻ'); // 12
221
+ parseRomanNumerals('invalid'); // NaN
222
+ ```
67
223
 
68
224
  [badge-npm]: https://img.shields.io/npm/v/numeric-quantity.svg?cacheSeconds=3600&logo=npm
@@ -0,0 +1 @@
1
+ export * from './numeric-quantity.cjs.development.js';
@@ -0,0 +1,225 @@
1
+ //#region src/types.d.ts
2
+ interface NumericQuantityOptions {
3
+ /**
4
+ * Round the result to this many decimal places. Defaults to 3; must
5
+ * be greater than or equal to zero.
6
+ *
7
+ * @default 3
8
+ */
9
+ round?: number | false;
10
+ /**
11
+ * Allow and ignore trailing invalid characters _à la_ `parseFloat`.
12
+ *
13
+ * @default false
14
+ */
15
+ allowTrailingInvalid?: boolean;
16
+ /**
17
+ * Attempt to parse Roman numerals if Arabic numeral parsing fails.
18
+ *
19
+ * @default false
20
+ */
21
+ romanNumerals?: boolean;
22
+ /**
23
+ * Generates a `bigint` value if the string represents
24
+ * a valid integer too large for the `number` type.
25
+ */
26
+ bigIntOnOverflow?: boolean;
27
+ /**
28
+ * Specifies which character ("." or ",") to treat as the decimal separator.
29
+ *
30
+ * @default "."
31
+ */
32
+ decimalSeparator?: "," | ".";
33
+ /**
34
+ * Allow and strip currency symbols (Unicode `\p{Sc}` category) from the
35
+ * start and/or end of the string.
36
+ *
37
+ * @default false
38
+ */
39
+ allowCurrency?: boolean;
40
+ /**
41
+ * Parse percentage strings by stripping the `%` suffix.
42
+ * - `'decimal'` or `true`: `"50%"` → `0.5` (divide by 100)
43
+ * - `'number'`: `"50%"` → `50` (strip `%`, keep value)
44
+ * - `false` or omitted: `"50%"` → `NaN` (default behavior)
45
+ *
46
+ * @default false
47
+ */
48
+ percentage?: "decimal" | "number" | boolean;
49
+ /**
50
+ * Return a verbose result object with additional parsing metadata.
51
+ *
52
+ * @default false
53
+ */
54
+ verbose?: boolean;
55
+ }
56
+ /**
57
+ * Resolves the return type of {@link numericQuantity} based on the options provided.
58
+ */
59
+ type NumericQuantityReturnType<T extends NumericQuantityOptions | undefined = undefined> = T extends {
60
+ verbose: true;
61
+ } ? NumericQuantityVerboseResult : T extends {
62
+ bigIntOnOverflow: true;
63
+ } ? number | bigint : number;
64
+ /**
65
+ * Verbose result returned when `verbose: true` is set.
66
+ */
67
+ interface NumericQuantityVerboseResult {
68
+ /** The parsed numeric value (NaN if invalid). */
69
+ value: number | bigint;
70
+ /** The original input string. */
71
+ input: string;
72
+ /** Currency symbol(s) stripped from the start, if any. */
73
+ currencyPrefix?: string;
74
+ /** Currency symbol(s) stripped from the end, if any. */
75
+ currencySuffix?: string;
76
+ /** True if a `%` suffix was stripped. */
77
+ percentageSuffix?: boolean;
78
+ /** Trailing invalid (usually non-numeric) characters detected in the input, if any. Populated even when `allowTrailingInvalid` is `false`. */
79
+ trailingInvalid?: string;
80
+ /** The leading sign character (`'-'` or `'+'`), if present. Omitted when no explicit sign was in the input. */
81
+ sign?: "-" | "+";
82
+ /** The whole-number part of a mixed fraction (e.g. `1` from `"1 2/3"`). Omitted for pure fractions, decimals, and integers. */
83
+ whole?: number;
84
+ /** The numerator of a fraction (e.g. `2` from `"1 2/3"`, or `1` from `"1/2"`). Always unsigned. */
85
+ numerator?: number;
86
+ /** The denominator of a fraction (e.g. `3` from `"1 2/3"`, or `2` from `"1/2"`). Always unsigned. */
87
+ denominator?: number;
88
+ }
89
+ /**
90
+ * Unicode vulgar fraction code points.
91
+ */
92
+ type VulgarFraction = "¼" | "½" | "¾" | "⅐" | "⅑" | "⅒" | "⅓" | "⅔" | "⅕" | "⅖" | "⅗" | "⅘" | "⅙" | "⅚" | "⅛" | "⅜" | "⅝" | "⅞" | "⅟";
93
+ /**
94
+ * Allowable Roman numeral characters (ASCII, uppercase only).
95
+ */
96
+ type RomanNumeralAscii = "I" | "V" | "X" | "L" | "C" | "D" | "M";
97
+ /**
98
+ * Unicode Roman numeral code points (uppercase and lowercase,
99
+ * representing 1-12, 50, 100, 500, and 1000).
100
+ */
101
+ type RomanNumeralUnicode = "Ⅰ" | "Ⅱ" | "Ⅲ" | "Ⅳ" | "Ⅴ" | "Ⅵ" | "Ⅶ" | "Ⅷ" | "Ⅸ" | "Ⅹ" | "Ⅺ" | "Ⅻ" | "Ⅼ" | "Ⅽ" | "Ⅾ" | "Ⅿ" | "ⅰ" | "ⅱ" | "ⅲ" | "ⅳ" | "ⅴ" | "ⅵ" | "ⅶ" | "ⅷ" | "ⅸ" | "ⅹ" | "ⅺ" | "ⅻ" | "ⅼ" | "ⅽ" | "ⅾ" | "ⅿ";
102
+ /**
103
+ * Union of ASCII and Unicode Roman numeral characters/code points.
104
+ */
105
+ type RomanNumeral = RomanNumeralAscii | RomanNumeralUnicode;
106
+ //#endregion
107
+ //#region src/constants.d.ts
108
+ /**
109
+ * Normalizes non-ASCII decimal digits to ASCII digits.
110
+ * Converts characters from Unicode decimal digit blocks (e.g., Arabic-Indic,
111
+ * Devanagari, Bengali) to their ASCII equivalents (0-9).
112
+ *
113
+ * All current Unicode \p{Nd} blocks are included in decimalDigitBlockStarts.
114
+ */
115
+ declare const normalizeDigits: (str: string) => string;
116
+ /**
117
+ * Map of Unicode fraction code points to their ASCII equivalents.
118
+ */
119
+ declare const vulgarFractionToAsciiMap: Record<VulgarFraction, `${number}/${number | ""}`>;
120
+ /**
121
+ * Captures the individual elements of a numeric string. Commas and underscores are allowed
122
+ * as separators, as long as they appear between digits and are not consecutive.
123
+ *
124
+ * Capture groups:
125
+ *
126
+ * | # | Description | Example(s) |
127
+ * | --- | ------------------------------------------------ | ------------------------------------------------------------------- |
128
+ * | `0` | entire string | `"2 1/3"` from `"2 1/3"` |
129
+ * | `1` | sign (`-` or `+`) | `"-"` from `"-2 1/3"` |
130
+ * | `2` | whole number or numerator | `"2"` from `"2 1/3"`; `"1"` from `"1/3"` |
131
+ * | `3` | entire fraction, decimal portion, or denominator | `" 1/3"` from `"2 1/3"`; `".33"` from `"2.33"`; `"/3"` from `"1/3"` |
132
+ *
133
+ * _Capture group 2 may include comma/underscore separators._
134
+ *
135
+ * @example
136
+ *
137
+ * ```ts
138
+ * numericRegex.exec("1") // [ "1", "1", null, null ]
139
+ * numericRegex.exec("1.23") // [ "1.23", "1", ".23", null ]
140
+ * numericRegex.exec("1 2/3") // [ "1 2/3", "1", " 2/3", " 2" ]
141
+ * numericRegex.exec("2/3") // [ "2/3", "2", "/3", null ]
142
+ * numericRegex.exec("2 / 3") // [ "2 / 3", "2", "/ 3", null ]
143
+ * ```
144
+ */
145
+ declare const numericRegex: RegExp;
146
+ /**
147
+ * Same as {@link numericRegex}, but allows (and ignores) trailing invalid characters.
148
+ * Capture group 7 contains the trailing invalid portion.
149
+ */
150
+ declare const numericRegexWithTrailingInvalid: RegExp;
151
+ /**
152
+ * Captures any Unicode vulgar fractions.
153
+ */
154
+ declare const vulgarFractionsRegex: RegExp;
155
+ type RomanNumeralSequenceFragment = `${RomanNumeralAscii}` | `${RomanNumeralAscii}${RomanNumeralAscii}` | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}` | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;
156
+ /**
157
+ * Map of Roman numeral sequences to their decimal equivalents.
158
+ */
159
+ declare const romanNumeralValues: { [k in RomanNumeralSequenceFragment]?: number };
160
+ /**
161
+ * Map of Unicode Roman numeral code points to their ASCII equivalents.
162
+ */
163
+ declare const romanNumeralUnicodeToAsciiMap: Record<RomanNumeralUnicode, keyof typeof romanNumeralValues>;
164
+ /**
165
+ * Captures all Unicode Roman numeral code points.
166
+ */
167
+ declare const romanNumeralUnicodeRegex: RegExp;
168
+ /**
169
+ * Captures a valid Roman numeral sequence.
170
+ *
171
+ * Capture groups:
172
+ *
173
+ * | # | Description | Example |
174
+ * | --- | --------------- | ------------------------ |
175
+ * | `0` | Entire string | "MCCXIV" from "MCCXIV" |
176
+ * | `1` | Thousands | "M" from "MCCXIV" |
177
+ * | `2` | Hundreds | "CC" from "MCCXIV" |
178
+ * | `3` | Tens | "X" from "MCCXIV" |
179
+ * | `4` | Ones | "IV" from "MCCXIV" |
180
+ *
181
+ * @example
182
+ *
183
+ * ```ts
184
+ * romanNumeralRegex.exec("M") // [ "M", "M", "", "", "" ]
185
+ * romanNumeralRegex.exec("XII") // [ "XII", "", "", "X", "II" ]
186
+ * romanNumeralRegex.exec("MCCXIV") // [ "MCCXIV", "M", "CC", "X", "IV" ]
187
+ * ```
188
+ */
189
+ declare const romanNumeralRegex: RegExp;
190
+ /**
191
+ * Default options for {@link numericQuantity}.
192
+ */
193
+ declare const defaultOptions: Required<NumericQuantityOptions>;
194
+ //#endregion
195
+ //#region src/isNumericQuantity.d.ts
196
+ /**
197
+ * Checks if a string represents a valid numeric quantity.
198
+ *
199
+ * Returns `true` if the string can be parsed as a number, `false` otherwise.
200
+ * Accepts the same options as `numericQuantity`.
201
+ */
202
+ declare const isNumericQuantity: (quantity: string | number, options?: NumericQuantityOptions) => boolean;
203
+ //#endregion
204
+ //#region src/numericQuantity.d.ts
205
+ /**
206
+ * Converts a string to a number, like an enhanced version of `parseFloat`.
207
+ *
208
+ * The string can include mixed numbers, vulgar fractions, or Roman numerals.
209
+ */
210
+ declare function numericQuantity(quantity: string | number): number;
211
+ declare function numericQuantity<T extends NumericQuantityOptions>(quantity: string | number, options: T): NumericQuantityReturnType<T>;
212
+ declare function numericQuantity(quantity: string | number, options?: NumericQuantityOptions): number;
213
+ //#endregion
214
+ //#region src/parseRomanNumerals.d.ts
215
+ /**
216
+ * Converts a string of Roman numerals to a number, like `parseInt`
217
+ * for Roman numerals. Uses modern, strict rules (only 1 to 3999).
218
+ *
219
+ * The string can include ASCII representations of Roman numerals
220
+ * or Unicode Roman numeral code points (`U+2160` through `U+217F`).
221
+ */
222
+ declare const parseRomanNumerals: (romanNumerals: string) => number;
223
+ //#endregion
224
+ export { NumericQuantityOptions, NumericQuantityReturnType, NumericQuantityVerboseResult, RomanNumeral, RomanNumeralAscii, RomanNumeralUnicode, VulgarFraction, defaultOptions, isNumericQuantity, normalizeDigits, numericQuantity, numericRegex, numericRegexWithTrailingInvalid, parseRomanNumerals, romanNumeralRegex, romanNumeralUnicodeRegex, romanNumeralUnicodeToAsciiMap, romanNumeralValues, vulgarFractionToAsciiMap, vulgarFractionsRegex };
225
+ //# sourceMappingURL=numeric-quantity.cjs.development.d.ts.map
@@ -138,7 +138,7 @@ const vulgarFractionToAsciiMap = {
138
138
  * | # | Description | Example(s) |
139
139
  * | --- | ------------------------------------------------ | ------------------------------------------------------------------- |
140
140
  * | `0` | entire string | `"2 1/3"` from `"2 1/3"` |
141
- * | `1` | "negative" dash | `"-"` from `"-2 1/3"` |
141
+ * | `1` | sign (`-` or `+`) | `"-"` from `"-2 1/3"` |
142
142
  * | `2` | whole number or numerator | `"2"` from `"2 1/3"`; `"1"` from `"1/3"` |
143
143
  * | `3` | entire fraction, decimal portion, or denominator | `" 1/3"` from `"2 1/3"`; `".33"` from `"2.33"`; `"/3"` from `"1/3"` |
144
144
  *
@@ -154,11 +154,12 @@ const vulgarFractionToAsciiMap = {
154
154
  * numericRegex.exec("2 / 3") // [ "2 / 3", "2", "/ 3", null ]
155
155
  * ```
156
156
  */
157
- 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)*)?$/;
157
+ 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)*)?$/;
158
158
  /**
159
159
  * Same as {@link numericRegex}, but allows (and ignores) trailing invalid characters.
160
+ * Capture group 7 contains the trailing invalid portion.
160
161
  */
161
- 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/].*)?/;
162
+ 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/].*)?/;
162
163
  /**
163
164
  * Captures any Unicode vulgar fractions.
164
165
  */
@@ -271,7 +272,10 @@ const defaultOptions = {
271
272
  allowTrailingInvalid: false,
272
273
  romanNumerals: false,
273
274
  bigIntOnOverflow: false,
274
- decimalSeparator: "."
275
+ decimalSeparator: ".",
276
+ allowCurrency: false,
277
+ percentage: false,
278
+ verbose: false
275
279
  };
276
280
 
277
281
  //#endregion
@@ -294,21 +298,68 @@ const parseRomanNumerals = (romanNumerals) => {
294
298
  //#endregion
295
299
  //#region src/numericQuantity.ts
296
300
  const spaceThenSlashRegex = /^\s*\//;
301
+ const currencyPrefixRegex = /^([-+]?)\s*(\p{Sc}+)\s*/u;
302
+ const currencySuffixRegex = /\s*(\p{Sc}+)$/u;
303
+ const percentageSuffixRegex = /%$/;
297
304
  function numericQuantity(quantity, options = defaultOptions) {
298
- if (typeof quantity === "number" || typeof quantity === "bigint") return quantity;
299
- let finalResult = NaN;
300
- const quantityAsString = normalizeDigits(`${quantity}`.replace(vulgarFractionsRegex, (_m, vf) => ` ${vulgarFractionToAsciiMap[vf]}`).replace("⁄", "/").trim());
301
- if (quantityAsString.length === 0) return NaN;
302
305
  const opts = {
303
306
  ...defaultOptions,
304
307
  ...options
305
308
  };
309
+ const originalInput = typeof quantity === "string" ? quantity : `${quantity}`;
310
+ let currencyPrefix;
311
+ let currencySuffix;
312
+ let percentageSuffix;
313
+ let trailingInvalid;
314
+ let parsedSign;
315
+ let parsedWhole;
316
+ let parsedNumerator;
317
+ let parsedDenominator;
318
+ const buildVerboseResult = (value) => {
319
+ const result = {
320
+ value,
321
+ input: originalInput
322
+ };
323
+ if (currencyPrefix) result.currencyPrefix = currencyPrefix;
324
+ if (currencySuffix) result.currencySuffix = currencySuffix;
325
+ if (percentageSuffix) result.percentageSuffix = percentageSuffix;
326
+ if (trailingInvalid) result.trailingInvalid = trailingInvalid;
327
+ if (parsedSign) result.sign = parsedSign;
328
+ if (parsedWhole !== void 0) result.whole = parsedWhole;
329
+ if (parsedNumerator !== void 0) result.numerator = parsedNumerator;
330
+ if (parsedDenominator !== void 0) result.denominator = parsedDenominator;
331
+ return result;
332
+ };
333
+ const returnValue = (value) => opts.verbose ? buildVerboseResult(value) : value;
334
+ if (typeof quantity === "number" || typeof quantity === "bigint") return returnValue(quantity);
335
+ let finalResult = NaN;
336
+ let workingString = `${quantity}`;
337
+ if (opts.allowCurrency) {
338
+ const prefixMatch = currencyPrefixRegex.exec(workingString);
339
+ if (prefixMatch && prefixMatch[2]) {
340
+ currencyPrefix = prefixMatch[2];
341
+ workingString = (prefixMatch[1] || "") + workingString.slice(prefixMatch[0].length);
342
+ }
343
+ }
344
+ if (opts.allowCurrency) {
345
+ const suffixMatch = currencySuffixRegex.exec(workingString);
346
+ if (suffixMatch) {
347
+ currencySuffix = suffixMatch[1];
348
+ workingString = workingString.slice(0, -suffixMatch[0].length);
349
+ }
350
+ }
351
+ if (opts.percentage && percentageSuffixRegex.test(workingString)) {
352
+ percentageSuffix = true;
353
+ workingString = workingString.slice(0, -1);
354
+ }
355
+ const quantityAsString = normalizeDigits(workingString.replace(vulgarFractionsRegex, (_m, vf) => ` ${vulgarFractionToAsciiMap[vf]}`).replace("⁄", "/").trim());
356
+ if (quantityAsString.length === 0) return returnValue(NaN);
306
357
  let normalizedString = quantityAsString;
307
358
  if (opts.decimalSeparator === ",") {
308
359
  const commaCount = (quantityAsString.match(/,/g) || []).length;
309
360
  if (commaCount === 1) normalizedString = quantityAsString.replaceAll(".", "_").replace(",", ".");
310
361
  else if (commaCount > 1) {
311
- if (!opts.allowTrailingInvalid) return NaN;
362
+ if (!opts.allowTrailingInvalid) return returnValue(NaN);
312
363
  const firstCommaIndex = quantityAsString.indexOf(",");
313
364
  const secondCommaIndex = quantityAsString.indexOf(",", firstCommaIndex + 1);
314
365
  const beforeSecondComma = quantityAsString.substring(0, secondCommaIndex).replaceAll(".", "_").replace(",", ".");
@@ -316,20 +367,30 @@ function numericQuantity(quantity, options = defaultOptions) {
316
367
  normalizedString = opts.allowTrailingInvalid ? beforeSecondComma + "&" + afterSecondComma : beforeSecondComma;
317
368
  } else normalizedString = quantityAsString.replaceAll(".", "_");
318
369
  }
319
- const regexResult = (opts.allowTrailingInvalid ? numericRegexWithTrailingInvalid : numericRegex).exec(normalizedString);
320
- if (!regexResult) return opts.romanNumerals ? parseRomanNumerals(quantityAsString) : NaN;
321
- const [, dash, ng1temp, ng2temp] = regexResult;
370
+ const regexResult = numericRegexWithTrailingInvalid.exec(normalizedString);
371
+ if (!regexResult) return returnValue(opts.romanNumerals ? parseRomanNumerals(quantityAsString) : NaN);
372
+ const rawTrailing = (regexResult[7] || normalizedString.slice(regexResult[0].length)).trim();
373
+ if (rawTrailing) {
374
+ trailingInvalid = rawTrailing;
375
+ if (!opts.allowTrailingInvalid) return returnValue(NaN);
376
+ }
377
+ const [, sign, ng1temp, ng2temp] = regexResult;
378
+ if (sign === "-" || sign === "+") parsedSign = sign;
322
379
  const numberGroup1 = ng1temp.replaceAll(",", "").replaceAll("_", "");
323
380
  const numberGroup2 = ng2temp === null || ng2temp === void 0 ? void 0 : ng2temp.replaceAll(",", "").replaceAll("_", "");
324
381
  if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith(".")) finalResult = 0;
325
382
  else {
326
383
  if (opts.bigIntOnOverflow) {
327
- const asBigInt = dash ? BigInt(`-${numberGroup1}`) : BigInt(numberGroup1);
328
- if (asBigInt > BigInt(Number.MAX_SAFE_INTEGER) || asBigInt < BigInt(Number.MIN_SAFE_INTEGER)) return asBigInt;
384
+ const asBigInt = sign === "-" ? BigInt(`-${numberGroup1}`) : BigInt(numberGroup1);
385
+ if (asBigInt > BigInt(Number.MAX_SAFE_INTEGER) || asBigInt < BigInt(Number.MIN_SAFE_INTEGER)) return returnValue(asBigInt);
329
386
  }
330
387
  finalResult = parseInt(numberGroup1);
331
388
  }
332
- if (!numberGroup2) return dash ? finalResult * -1 : finalResult;
389
+ if (!numberGroup2) {
390
+ finalResult = sign === "-" ? finalResult * -1 : finalResult;
391
+ if (percentageSuffix && opts.percentage !== "number") finalResult = finalResult / 100;
392
+ return returnValue(finalResult);
393
+ }
333
394
  const roundingFactor = opts.round === false ? NaN : parseFloat(`1e${Math.floor(Math.max(0, opts.round))}`);
334
395
  if (numberGroup2.startsWith(".") || numberGroup2.startsWith("e") || numberGroup2.startsWith("E")) {
335
396
  const decimalValue = parseFloat(`${finalResult}${numberGroup2}`);
@@ -337,16 +398,40 @@ function numericQuantity(quantity, options = defaultOptions) {
337
398
  } else if (spaceThenSlashRegex.test(numberGroup2)) {
338
399
  const numerator = parseInt(numberGroup1);
339
400
  const denominator = parseInt(numberGroup2.replace("/", ""));
401
+ parsedNumerator = numerator;
402
+ parsedDenominator = denominator;
340
403
  finalResult = isNaN(roundingFactor) ? numerator / denominator : Math.round(numerator * roundingFactor / denominator) / roundingFactor;
341
404
  } else {
342
405
  const [numerator, denominator] = numberGroup2.split("/").map((v) => parseInt(v));
406
+ parsedWhole = finalResult;
407
+ parsedNumerator = numerator;
408
+ parsedDenominator = denominator;
343
409
  finalResult += isNaN(roundingFactor) ? numerator / denominator : Math.round(numerator * roundingFactor / denominator) / roundingFactor;
344
410
  }
345
- return dash ? finalResult * -1 : finalResult;
411
+ finalResult = sign === "-" ? finalResult * -1 : finalResult;
412
+ if (percentageSuffix && opts.percentage !== "number") finalResult = isNaN(roundingFactor) ? finalResult / 100 : Math.round(finalResult / 100 * roundingFactor) / roundingFactor;
413
+ return returnValue(finalResult);
346
414
  }
347
415
 
416
+ //#endregion
417
+ //#region src/isNumericQuantity.ts
418
+ /**
419
+ * Checks if a string represents a valid numeric quantity.
420
+ *
421
+ * Returns `true` if the string can be parsed as a number, `false` otherwise.
422
+ * Accepts the same options as `numericQuantity`.
423
+ */
424
+ const isNumericQuantity = (quantity, options) => {
425
+ const result = numericQuantity(quantity, {
426
+ ...options,
427
+ verbose: false
428
+ });
429
+ return typeof result === "bigint" || !isNaN(result);
430
+ };
431
+
348
432
  //#endregion
349
433
  exports.defaultOptions = defaultOptions;
434
+ exports.isNumericQuantity = isNumericQuantity;
350
435
  exports.normalizeDigits = normalizeDigits;
351
436
  exports.numericQuantity = numericQuantity;
352
437
  exports.numericRegex = numericRegex;