Fun with Python: Converting To and From Roman Numerals
Hey everyone,
It's been a little while since I've done a "Fun with Python" post, with the last one being the word_clock.py
script that tells time with words. Today, I've got another fun little project to share: a command-line tool I wrote to convert integers to and from Roman numerals.
Roman numerals are an interesting programming puzzle. It's not just a simple one-to-one mapping of numbers to letters; you have to account for the subtractive principle where "IV" is 4 (not "IIII") and "CM" is 900 (not "DCCCC"). It makes for a great little logic challenge.
The roman.py
Script
I put together a simple Python script to handle these conversions. It's a command-line tool that can take an integer and give you the Roman numeral, or take a Roman numeral and give you the integer.
A neat feature I included is support for larger numbers using the vinculum (or overbar) notation, where a bar over a numeral multiplies its value by 1,000. So, X̅
is 10,000, C̅
is 100,000, and so on.
How It Works
- Integer to Roman: To convert a number like
1994
, the script works its way down from the largest values. It sees that1994
is bigger than1000
, so it adds an "M" and subtracts 1000, leaving 994. Then it sees900
, adds "CM" and subtracts 900, leaving 94. It continues this for90
("XC") and4
("IV") until it gets to zero, building the final string:MCMXCIV
. - Roman to Integer: Going the other way, it reads the Roman numeral string from left to right, looking for the largest symbol it can match first (so it will see "CM" before it sees "C"). It adds that symbol's value to a running total and then chops that symbol off the string, repeating the process until the string is empty.
The Code
Here is the full script. It's self-contained and doesn't require any external libraries.
#!/usr/bin/env python3
import argparse
from typing import List, Tuple
# Ordered list of Roman numeral symbols (value, symbol) in descending order.
# Includes overbar (vinculum) characters for larger numbers.
SYMBOLS: List[Tuple[int, str]] = [
(1000000, "M̅"),
(100000, "C̅"),
(10000, "X̅"),
(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"),
]
# Derived mapping for quick symbol → value lookups.
SYMBOL_TO_VALUE = {s: v for v, s in SYMBOLS}
def int_to_roman(num: int) -> str:
"""
Convert an integer to a Roman numeral.
"""
if not 0 < num < 4000000: # Practical upper bound
raise ValueError("Integer must be between 1 and 3,999,999.")
roman_numeral = ""
# Build the numeral greedily from largest to smallest value.
for value, symbol in SYMBOLS:
while num >= value:
roman_numeral += symbol
num -= value
return roman_numeral
def roman_to_int(roman: str) -> int:
"""
Convert a Roman numeral to an integer.
"""
if not roman:
raise ValueError("Roman numeral cannot be empty.")
roman = roman.upper()
# Sort symbols by length so multi-char symbols (like 'CM') match first.
symbols = sorted(SYMBOL_TO_VALUE.keys(), key=len, reverse=True)
value = 0
while roman:
matched = False
for symbol in symbols:
if roman.startswith(symbol):
value += SYMBOL_TO_VALUE[symbol]
roman = roman[len(symbol) :]
matched = True
break
if not matched:
raise ValueError(f"Invalid character or sequence in Roman numeral: '{roman}'")
return value
def main() -> int:
"""Entry-point for command-line interface."""
parser = argparse.ArgumentParser(
description="Convert between integers and Roman numerals."
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
"-i", "--integer", type=int, help="Integer to convert to Roman numeral."
)
group.add_argument(
"-r", "--roman", type=str, help="Roman numeral to convert to integer."
)
args = parser.parse_args()
try:
if args.integer is not None:
print(int_to_roman(args.integer))
else:
print(roman_to_int(args.roman))
except ValueError as exc:
parser.error(str(exc))
return 0
if __name__ == "__main__":
main()
How to Use It
Using it from your terminal is straightforward:
To convert an integer to a Roman numeral:
python3 roman.py --integer 2025
Output: MMXXV
To convert a Roman numeral to an integer:
python3 roman.py --roman MCMXCIV
Output: 1994
And here's an example with a larger number:
python3 roman.py -i 12345
Output: X̅MMCCCXLV
It's a simple, fun project and a great way to practice some basic algorithm logic. Hope you enjoy it!
As always,
Michael Garcia a.k.a. TheCrazyGM
All the vibe coders should also learn to code with great content like this! We can make a collextion later.
!PAKX
!PIMP
!PIZZA
View or trade
PAKX
tokens.Use !PAKX command if you hold enough balance to call for a @pakx vote on worthy posts! More details available on PAKX Blog.
$PIZZA slices delivered:
@ecoinstant(2/20) tipped @thecrazygm
Come get MOONed!
While I never learned Roman numerals in depth, I had no idea that they presented such interesting challenges to converting to and from Arabic numerals. Very cool, I learned something! 😁 🙏 💚 ✨ 🤙