Toolmingo
Guides6 min read

Roman Numeral Converter: How Roman Numerals Work

Learn how Roman numerals work, the rules for reading and writing them, and how to convert between Roman numerals and decimal numbers in JavaScript, Python, Go, and PHP.

Roman numerals are still everywhere — clock faces, film copyright dates, Super Bowl numbers, chapter headings, and formal outlines. Once you understand the seven symbols and a handful of rules, you can read and write any Roman numeral instantly.

The seven symbols

Symbol Value
I 1
V 5
X 10
L 50
C 100
D 500
M 1000

That's the entire alphabet. Everything else is built by combining and ordering these symbols.

Additive notation

When symbols are listed from largest to smallest, you simply add them up:

VIII = 5 + 1 + 1 + 1 = 8
LXIII = 50 + 10 + 1 + 1 + 1 = 63
MCCC = 1000 + 100 + 100 + 100 = 1300

Subtractive notation (the exceptions)

When a smaller symbol appears directly before a larger one, you subtract it. There are exactly six subtractive pairs:

Pair Value Instead of
IV 4 IIII
IX 9 VIIII
XL 40 XXXX
XC 90 LXXXX
CD 400 CCCC
CM 900 DCCCC

Only these six pairs are valid. You will never see IL (49), IC (99), or VX (5 before 10) in standard Roman numerals.

Repetition rules

  • I, X, C, M can be repeated up to three times in a row (XXXIX = 39; MMMCMXCIX = 3999).
  • V, L, D are never repeated — you can't write VV for 10; use X.

The maximum value expressible with standard symbols is 3999 (MMMCMXCIX). For higher numbers, a bar over a symbol multiplies it by 1000 (V̄ = 5000), but that notation is rare outside classical texts.

Algorithm: decimal → Roman numeral

The cleanest approach is a greedy algorithm using a lookup table of all 13 values (including the six subtractive pairs) sorted from largest to smallest:

values = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
symbols = [M, CM, D, CD, C, XC, L, XL, X, IX, V, IV, I]

result = ""
for each (value, symbol) in zip(values, symbols):
    while number >= value:
        result += symbol
        number -= value
return result

For 2024:

  • 2024 ≥ 1000 → append M, 2024 − 1000 = 1024
  • 1024 ≥ 1000 → append M, 1024 − 1000 = 24
  • 24 ≥ 10 → append X, 24 − 10 = 14
  • 14 ≥ 10 → append X, 14 − 10 = 4
  • 4 ≥ 4 → append IV, 4 − 4 = 0
  • Result: MMXXIV

Algorithm: Roman numeral → decimal

Read left to right. If the current symbol is less than the next one, subtract it; otherwise add it:

total = 0
for i in range(len(roman)):
    if i + 1 < len(roman) and value[roman[i]] < value[roman[i+1]]:
        total -= value[roman[i]]
    else:
        total += value[roman[i]]
return total

For MCMXCIX (1999):

  • M(1000) < C(900)? No → +1000 → 1000
  • C(100) < M(1000)? Yes → −100 → 900
  • M(1000) < X(90)? No → +1000 → 1900
  • X(10) < C(100)? Yes → −10 → 1890
  • C(100) < I(1)? No → +100 → 1990
  • I(1) < X(10)? Yes → −1 → 1989
  • X(10) → +10 → 1999 ✓

Code examples

JavaScript

function toRoman(num) {
  const vals =   [1000,900,500,400,100,90,50,40,10,9,5,4,1];
  const syms =   ['M','CM','D','CD','C','XC','L','XL','X','IX','V','IV','I'];
  let result = '';
  for (let i = 0; i < vals.length; i++) {
    while (num >= vals[i]) {
      result += syms[i];
      num -= vals[i];
    }
  }
  return result;
}

function fromRoman(s) {
  const map = { I:1, V:5, X:10, L:50, C:100, D:500, M:1000 };
  return [...s].reduce((total, ch, i, arr) => {
    const cur = map[ch], next = map[arr[i + 1]];
    return total + (next > cur ? -cur : cur);
  }, 0);
}

console.log(toRoman(2024));   // MMXXIV
console.log(fromRoman('MMXXIV')); // 2024

Python

def to_roman(num: int) -> str:
    vals  = [1000,900,500,400,100,90,50,40,10,9,5,4,1]
    syms  = ['M','CM','D','CD','C','XC','L','XL','X','IX','V','IV','I']
    result = ''
    for v, s in zip(vals, syms):
        while num >= v:
            result += s
            num -= v
    return result

def from_roman(s: str) -> int:
    roman = {'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000}
    total = 0
    prev = 0
    for ch in reversed(s):
        cur = roman[ch]
        total += cur if cur >= prev else -cur
        prev = cur
    return total

print(to_roman(2024))        # MMXXIV
print(from_roman('MMXXIV'))  # 2024

Go

func toRoman(num int) string {
    vals := []int{1000,900,500,400,100,90,50,40,10,9,5,4,1}
    syms := []string{"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"}
    var b strings.Builder
    for i, v := range vals {
        for num >= v {
            b.WriteString(syms[i])
            num -= v
        }
    }
    return b.String()
}

func fromRoman(s string) int {
    roman := map[byte]int{'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000}
    total, prev := 0, 0
    for i := len(s) - 1; i >= 0; i-- {
        cur := roman[s[i]]
        if cur < prev { total -= cur } else { total += cur }
        prev = cur
    }
    return total
}

PHP

function toRoman(int $num): string {
    $vals = [1000,900,500,400,100,90,50,40,10,9,5,4,1];
    $syms = ['M','CM','D','CD','C','XC','L','XL','X','IX','V','IV','I'];
    $result = '';
    foreach ($vals as $i => $v) {
        while ($num >= $v) { $result .= $syms[$i]; $num -= $v; }
    }
    return $result;
}

function fromRoman(string $s): int {
    $map = ['I'=>1,'V'=>5,'X'=>10,'L'=>50,'C'=>100,'D'=>500,'M'=>1000];
    $chars = str_split($s);
    $total = 0;
    foreach ($chars as $i => $ch) {
        $cur  = $map[$ch];
        $next = isset($chars[$i+1]) ? $map[$chars[$i+1]] : 0;
        $total += $next > $cur ? -$cur : $cur;
    }
    return $total;
}

Common Roman numerals quick reference

Decimal Roman Where you see it
4 IV Clock faces
9 IX Super Bowl IX
14 XIV Chapter XIV
40 XL XL (extra large)
49 XLIX Super Bowl XLIX
100 C Century C
400 CD CD (compact disc acronym aside)
500 D D-Day references
1999 MCMXCIX Film copyright dates
2024 MMXXIV Olympics Paris
3999 MMMCMXCIX Maximum standard value

FAQ

Why does my clock say IIII instead of IV? Many clock makers use IIII for symmetry — it visually balances the VIII on the opposite side. Both forms appear historically.

Can Roman numerals express zero or negative numbers? No. The system has no zero and predates negative number notation. For zero, Romans used the word nulla.

What's the largest Roman numeral? With standard unbarred symbols: 3999 (MMMCMXCIX). Ancient texts used overlines (vinculum) to multiply by 1000, reaching millions.

Are lowercase roman numerals different? Not in value — i, v, x, l, c, d, m are the same numerals. Lowercase is common in front matter (page iii), lists, and formal outlines.

Why does the Super Bowl use Roman numerals? The NFL adopted them in 1971 to make each game feel like a unique event rather than "season 5". They dropped them briefly for Super Bowl 50 (L looked too plain), then brought them back for LI.


Use the Roman Numeral Converter to convert any number instantly — no memorisation needed.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools