PythonMastery

Roman numeral converter in Python

Convert a number from 1 to 3999 into Roman numerals, such as 2024 to MMXXIV, and convert Roman numerals back into numbers.

Roman numerals are written from the largest value to the smallest. Six pairs use subtraction: IV is 4, IX is 9, XL is 40, XC is 90, CD is 400 and CM is 900. This program converts in both directions.

The program

python
NUMERALS = [
    (1000, "M"), (900, "CM"), (500, "D"), (400, "CD"),
    (100, "C"), (90, "XC"), (50, "L"), (40, "XL"),
    (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I"),
]
VALUES = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000}


def to_roman(n):
    if not 1 <= n <= 3999:
        raise ValueError("Roman numerals here cover 1 to 3999")
    result = ""
    for value, symbol in NUMERALS:
        while n >= value:
            result += symbol
            n -= value
    return result


def from_roman(text):
    total = 0
    for i, letter in enumerate(text):
        value = VALUES[letter]
        # a smaller letter before a larger one is subtracted: the I in IV
        if i + 1 < len(text) and VALUES[text[i + 1]] > value:
            total -= value
        else:
            total += value
    return total


for n in [4, 9, 14, 40, 1994, 2024, 3999]:
    roman = to_roman(n)
    print(f"{n:5} {roman:12} {from_roman(roman)}")
output
    4 IV           4
    9 IX           9
   14 XIV          14
   40 XL           40
 1994 MCMXCIV      1994
 2024 MMXXIV       2024
 3999 MMMCMXCIX    3999

How it works

from_roman("IIII") returns 4 without any error, even though the standard form is IV. To reject numerals that are not in standard form, convert the answer back with to_roman() and check that you get the same text.

Learn the idea: Loops: for, while, and the Pythonic Patterns, Dictionaries, Tuples: Lists That Can't Be Changed