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 3999How it works
to_roman()goes through the list from 1000 down to 1. While the number is at least as large as the current value, it adds that symbol and subtracts the value. 1994 becomes M (994 left), CM (94 left), XC (4 left), IV.- The subtraction pairs (CM, CD, XC, XL, IX, IV) are in the list as if they were single symbols, so the loop never needs a special case for them.
from_roman()reads one letter at a time. Normally it adds the letter's value. When the next letter is larger, as inIVorCM, it subtracts the value instead.- There is no Roman numeral for 0 or for negative numbers, and the standard form stops at 3999 (MMMCMXCIX), so other numbers raise a
ValueError.
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.