/^/
HardNumbers5 min read

Roman Numerals Regex

Validates well-formed Roman numerals from 1 to 3999 written with standard subtractive notation (e.g. IV, IX, XL, XC, CD, CM).

#roman-numerals#numbers#validation#historical#regex#string-matching

Regex Pattern

^(?=[MDCLXVI])M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$

Pattern Breakdown

Hover over a token to see what it does.

^(?=[MDCLXVI])M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$
TokenMeaning
^Anchors the match to the start of the string
(?=[MDCLXVI])Lookahead requiring at least one valid Roman numeral character, rejecting an empty match
M{0,3}Zero to three literal M characters, representing thousands (1000-3000)
(CM|CD|D?C{0,3})Hundreds place: CM (900), CD (400), or an optional D (500) followed by zero to three Cs (100 each)
(XC|XL|L?X{0,3})Tens place: XC (90), XL (40), or an optional L (50) followed by zero to three Xs (10 each)
(IX|IV|V?I{0,3})Ones place: IX (9), IV (4), or an optional V (5) followed by zero to three Is (1 each)
$Anchors the match to the end of the string

Detailed Explanation

What it does

This pattern validates that a string is a syntactically correct Roman numeral in the range 1-3999, using standard additive and subtractive notation. It rejects malformed strings such as repeated subtractive pairs, too many repeated symbols, or numerals in the wrong order.

Why it works

Roman numerals are built from four place-value groups (thousands, hundreds, tens, ones), each of which has exactly one of a small set of valid spellings. The pattern models each group as its own alternation: a run of the base symbol repeated up to three times, an optional 'five' symbol prepended, or one of the two subtractive forms (four and nine). Concatenating the four groups in order and anchoring both ends means the whole string must be composed of exactly these valid group spellings with nothing left over. The leading lookahead prevents the pattern from accepting an empty string, since every group is independently optional.

Common use cases

  • Validating Roman numeral input on outline/heading numbering tools
  • Parsing chapter or clock-face numerals in document processing pipelines
  • Form validation for historical or ceremonial date fields (e.g. movie copyright years, monarch ordinals)
  • Sanitizing user input before converting Roman numerals to integers

Edge cases

  • Numbers requiring four or more of the same symbol in a row, like 'IIII' for 4, are correctly rejected in favor of 'IV'
  • Invalid subtractive combinations such as 'IL' (attempting 49) or 'VX' are rejected because only IV, IX, XL, XC, CD, and CM are recognized
  • Lowercase input like 'xiv' will not match since the character classes are uppercase only; add the 'i' flag if lowercase should be accepted
  • The pattern has no explicit upper bound check beyond structure, but structurally it cannot exceed 3999 (MMMCMXCIX) since M is capped at three repetitions
  • An empty string is rejected by the leading lookahead rather than silently matching

Limitations

  • Does not validate numerals above 3999, which historically use a vinculum (overline) notation not representable in plain ASCII
  • Does not convert the numeral to its integer value; a separate parsing step is needed for that
  • Case sensitivity means mixed-case input like 'Iv' will fail unless normalized or the 'i' flag is added
  • Some historical or non-standard Roman numeral variants (e.g. 'IIIX' style seen on clocks) are intentionally rejected as non-canonical

Interactive Tester

Edit the pattern or text below — matching runs live in your browser.

III IV IX

Test Cases

Editable — add your own inputs to see if they pass.

InputExpectedResult
Pass
Pass
Pass
Pass
Pass
Pass
Pass
Pass
Pass
Pass
Pass

Language Variants

Production-ready examples in 12 languages.

const romanRegex = /^(?=[MDCLXVI])M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/;
console.log(romanRegex.test('MCMXCIV')); // true

Common Mistakes

Using a naive pattern like [IVXLCDM]+ that accepts any combination of letters, including invalid sequences like 'IIIIVV'

Fix: Model each place value (thousands, hundreds, tens, ones) as its own group with the correct additive/subtractive alternatives

Forgetting the leading lookahead and allowing the empty string to match since every group is independently optional

Fix: Add a lookahead like (?=[MDCLXVI]) to require at least one valid symbol before the optional groups

Assuming the regex also converts the numeral to an integer

Fix: Use the regex purely for validation and pair it with a separate lookup-based conversion function

Performance Notes

  • Each place-value group is a bounded alternation with no nested quantifiers, so the pattern has linear-time matching with no catastrophic backtracking risk
  • The {0,3} repetition counts are small and fixed, keeping the search space tiny even on pathological input
  • Anchoring with ^ and $ plus the leading lookahead lets the engine reject non-Roman-numeral strings almost immediately

Browser Compatibility

EngineSupportedNotes
ChromeYes
FirefoxYes
SafariYes
EdgeYes
Node.jsYes