numeric-quantity
Version:
Number parser with support for mixed numbers, vulgar fractions, and Roman numerals
245 lines • 10.2 kB
text/typescript
//#region src/types.d.ts
interface NumericQuantityOptions {
/**
* Round the result to this many decimal places. Defaults to 3; must
* be greater than or equal to zero.
*
* @default 3
*/
round?: number | false;
/**
* Allow and ignore trailing invalid characters _à la_ `parseFloat`.
*
* @default false
*/
allowTrailingInvalid?: boolean;
/**
* Attempt to parse Roman numerals if Arabic numeral parsing fails.
*
* @default false
*/
romanNumerals?: boolean;
/**
* Generates a `bigint` value if the string represents
* a valid integer too large for the `number` type.
*/
bigIntOnOverflow?: boolean;
/**
* Specifies which character ("." or ",") to treat as the decimal separator.
*
* @default "."
*/
decimalSeparator?: "," | ".";
/**
* Allow and strip currency symbols (Unicode `\p{Sc}` category) from the
* start and/or end of the string.
*
* @default false
*/
allowCurrency?: boolean;
/**
* Parse percentage strings by stripping the `%` suffix.
* - `'decimal'` or `true`: `"50%"` → `0.5` (divide by 100)
* - `'number'`: `"50%"` → `50` (strip `%`, keep value)
* - `false` or omitted: `"50%"` → `NaN` (default behavior)
*
* @default false
*/
percentage?: "decimal" | "number" | boolean;
/**
* Return a verbose result object with additional parsing metadata.
*
* @default false
*/
verbose?: boolean;
}
/**
* Resolves the return type of {@link numericQuantity} based on the options provided.
*/
type NumericQuantityReturnType<T extends NumericQuantityOptions | undefined = undefined> = T extends {
verbose: true;
} ? NumericQuantityVerboseResult : T extends {
bigIntOnOverflow: true;
} ? number | bigint : number;
/**
* Verbose result returned when `verbose: true` is set.
*/
interface NumericQuantityVerboseResult {
/** The parsed numeric value (NaN if invalid). */
value: number | bigint;
/** The original input string. */
input: string;
/** Currency symbol(s) stripped from the start, if any. */
currencyPrefix?: string;
/** Currency symbol(s) stripped from the end, if any. */
currencySuffix?: string;
/** True if a `%` suffix was stripped. */
percentageSuffix?: boolean;
/** Trailing invalid (usually non-numeric) characters detected in the input, if any. Populated even when `allowTrailingInvalid` is `false`. */
trailingInvalid?: string;
/** The leading sign character (`'-'` or `'+'`), if present. Omitted when no explicit sign was in the input. */
sign?: "-" | "+";
/** The whole-number part of a mixed fraction (e.g. `1` from `"1 2/3"`). Omitted for pure fractions, decimals, and integers. */
whole?: number;
/** The numerator of a fraction (e.g. `2` from `"1 2/3"`, or `1` from `"1/2"`). Always unsigned. */
numerator?: number;
/** The denominator of a fraction (e.g. `3` from `"1 2/3"`, or `2` from `"1/2"`). Always unsigned. */
denominator?: number;
}
/**
* Unicode vulgar fraction code points.
*/
type VulgarFraction = "¼" | "½" | "¾" | "⅐" | "⅑" | "⅒" | "⅓" | "⅔" | "⅕" | "⅖" | "⅗" | "⅘" | "⅙" | "⅚" | "⅛" | "⅜" | "⅝" | "⅞" | "⅟";
/**
* Unicode superscript digit code points.
*/
type SuperscriptDigit = "⁰" | "¹" | "²" | "³" | "⁴" | "⁵" | "⁶" | "⁷" | "⁸" | "⁹";
/**
* Unicode subscript digit code points.
*/
type SubscriptDigit = "₀" | "₁" | "₂" | "₃" | "₄" | "₅" | "₆" | "₇" | "₈" | "₉";
/**
* Allowable Roman numeral characters (ASCII, uppercase only).
*/
type RomanNumeralAscii = "I" | "V" | "X" | "L" | "C" | "D" | "M";
/**
* Unicode Roman numeral code points (uppercase and lowercase,
* representing 1-12, 50, 100, 500, and 1000).
*/
type RomanNumeralUnicode = "Ⅰ" | "Ⅱ" | "Ⅲ" | "Ⅳ" | "Ⅴ" | "Ⅵ" | "Ⅶ" | "Ⅷ" | "Ⅸ" | "Ⅹ" | "Ⅺ" | "Ⅻ" | "Ⅼ" | "Ⅽ" | "Ⅾ" | "Ⅿ" | "ⅰ" | "ⅱ" | "ⅲ" | "ⅳ" | "ⅴ" | "ⅵ" | "ⅶ" | "ⅷ" | "ⅸ" | "ⅹ" | "ⅺ" | "ⅻ" | "ⅼ" | "ⅽ" | "ⅾ" | "ⅿ";
/**
* Union of ASCII and Unicode Roman numeral characters/code points.
*/
type RomanNumeral = RomanNumeralAscii | RomanNumeralUnicode;
//#endregion
//#region src/constants.d.ts
/**
* Normalizes non-ASCII decimal digits to ASCII digits.
* Converts characters from Unicode decimal digit blocks (e.g., Arabic-Indic,
* Devanagari, Bengali) to their ASCII equivalents (0-9).
*
* All current Unicode \p{Nd} blocks are included in decimalDigitBlockStarts.
*/
declare const normalizeDigits: (str: string) => string;
/**
* Map of Unicode superscript and subscript digit code points to ASCII digits.
*/
declare const superSubDigitToAsciiMap: Record<SuperscriptDigit | SubscriptDigit, string>;
/**
* Captures Unicode superscript and subscript digits.
*/
declare const superSubDigitsRegex: RegExp;
/**
* Map of Unicode fraction code points to their ASCII equivalents.
*/
declare const vulgarFractionToAsciiMap: Record<VulgarFraction, `${number}/${number | ""}`>;
/**
* Captures the individual elements of a numeric string. Commas and underscores are allowed
* as separators, as long as they appear between digits and are not consecutive.
*
* Capture groups:
*
* | # | Description | Example(s) |
* | --- | ------------------------------------------------ | ------------------------------------------------------------------- |
* | `0` | entire string | `"2 1/3"` from `"2 1/3"` |
* | `1` | sign (`-` or `+`) | `"-"` from `"-2 1/3"` |
* | `2` | whole number or numerator | `"2"` from `"2 1/3"`; `"1"` from `"1/3"` |
* | `3` | entire fraction, decimal portion, or denominator | `" 1/3"` from `"2 1/3"`; `".33"` from `"2.33"`; `"/3"` from `"1/3"` |
*
* _Capture group 2 may include comma/underscore separators._
*
* @example
*
* ```ts
* numericRegex.exec("1") // [ "1", "1", null, null ]
* numericRegex.exec("1.23") // [ "1.23", "1", ".23", null ]
* numericRegex.exec("1 2/3") // [ "1 2/3", "1", " 2/3", " 2" ]
* numericRegex.exec("2/3") // [ "2/3", "2", "/3", null ]
* numericRegex.exec("2 / 3") // [ "2 / 3", "2", "/ 3", null ]
* ```
*/
declare const numericRegex: RegExp;
/**
* Same as {@link numericRegex}, but allows (and ignores) trailing invalid characters.
* Capture group 7 contains the trailing invalid portion.
*/
declare const numericRegexWithTrailingInvalid: RegExp;
/**
* Captures any Unicode vulgar fractions.
*/
declare const vulgarFractionsRegex: RegExp;
type RomanNumeralSequenceFragment = `${RomanNumeralAscii}` | `${RomanNumeralAscii}${RomanNumeralAscii}` | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}` | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;
/**
* Map of Roman numeral sequences to their decimal equivalents.
*/
declare const romanNumeralValues: { [k in RomanNumeralSequenceFragment]?: number };
/**
* Map of Unicode Roman numeral code points to their ASCII equivalents.
*/
declare const romanNumeralUnicodeToAsciiMap: Record<RomanNumeralUnicode, keyof typeof romanNumeralValues>;
/**
* Captures all Unicode Roman numeral code points.
*/
declare const romanNumeralUnicodeRegex: RegExp;
/**
* Captures a valid Roman numeral sequence.
*
* Capture groups:
*
* | # | Description | Example |
* | --- | --------------- | ------------------------ |
* | `0` | Entire string | "MCCXIV" from "MCCXIV" |
* | `1` | Thousands | "M" from "MCCXIV" |
* | `2` | Hundreds | "CC" from "MCCXIV" |
* | `3` | Tens | "X" from "MCCXIV" |
* | `4` | Ones | "IV" from "MCCXIV" |
*
* @example
*
* ```ts
* romanNumeralRegex.exec("M") // [ "M", "M", "", "", "" ]
* romanNumeralRegex.exec("XII") // [ "XII", "", "", "X", "II" ]
* romanNumeralRegex.exec("MCCXIV") // [ "MCCXIV", "M", "CC", "X", "IV" ]
* ```
*/
declare const romanNumeralRegex: RegExp;
/**
* Default options for {@link numericQuantity}.
*/
declare const defaultOptions: Required<NumericQuantityOptions>;
//#endregion
//#region src/isNumericQuantity.d.ts
/**
* Checks if a string represents a valid numeric quantity.
*
* Returns `true` if the string can be parsed as a number, `false` otherwise.
* Accepts the same options as `numericQuantity`.
*/
declare const isNumericQuantity: (quantity: string | number, options?: NumericQuantityOptions) => boolean;
//#endregion
//#region src/numericQuantity.d.ts
/**
* Converts a string to a number, like an enhanced version of `parseFloat`.
*
* The string can include mixed numbers, vulgar fractions, or Roman numerals.
* Input is expected to be a `string`, but will be coerced to `string` if necessary.
*
* @param quantity - The value to parse as a numeric quantity.
* @param options - Optional settings to control parsing behavior.
*/
declare function numericQuantity(quantity: unknown): number;
declare function numericQuantity<T extends NumericQuantityOptions>(quantity: unknown, options: T): NumericQuantityReturnType<T>;
declare function numericQuantity(quantity: unknown, options?: NumericQuantityOptions): number;
//#endregion
//#region src/parseRomanNumerals.d.ts
/**
* Converts a string of Roman numerals to a number, like `parseInt`
* for Roman numerals. Uses modern, strict rules (only 1 to 3999).
*
* The string can include ASCII representations of Roman numerals
* or Unicode Roman numeral code points (`U+2160` through `U+217F`).
*/
declare const parseRomanNumerals: (romanNumerals: string) => number;
//#endregion
export { NumericQuantityOptions, NumericQuantityReturnType, NumericQuantityVerboseResult, RomanNumeral, RomanNumeralAscii, RomanNumeralUnicode, SubscriptDigit, SuperscriptDigit, VulgarFraction, defaultOptions, isNumericQuantity, normalizeDigits, numericQuantity, numericRegex, numericRegexWithTrailingInvalid, parseRomanNumerals, romanNumeralRegex, romanNumeralUnicodeRegex, romanNumeralUnicodeToAsciiMap, romanNumeralValues, superSubDigitToAsciiMap, superSubDigitsRegex, vulgarFractionToAsciiMap, vulgarFractionsRegex };
//# sourceMappingURL=numeric-quantity.production.d.mts.map