create account

Fun with Python: Converting To and From Roman Numerals by thecrazygm

View this thread on: hive.blogpeakd.comecency.com
· @thecrazygm · (edited)
$10.23
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

![Well beyond the Roman Era](https://files.peakd.com/file/peakd-hive/thecrazygm/AJoJbbzpWpDQFSrZeCffTahwaLQoFb59zmHRr6HnzicLULHmMHmVSbRTezVH99i.png)

- **Integer to Roman:** To convert a number like `1994`, the script works its way down from the largest values. It sees that `1994` is bigger than `1000`, so it adds an "M" and subtracts 1000, leaving 994. Then it sees `900`, adds "CM" and subtracts 900, leaving 94. It continues this for `90` ("XC") and `4` ("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.

```python
#!/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:

```bash
python3 roman.py --integer 2025
```

Output: `MMXXV`

To convert a Roman numeral to an integer:

```bash
python3 roman.py --roman MCMXCIV
```

Output: `1994`

And here's an example with a larger number:

```bash
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
👍  , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , and 130 others
properties (23)
authorthecrazygm
permlinkfun-with-python-converting-to-and-from-roman-numerals
categoryhive-186392
json_metadata{"app":"peakd/2025.6.1","format":"markdown","image":["https://files.peakd.com/file/peakd-hive/thecrazygm/AJoJbbzpWpDQFSrZeCffTahwaLQoFb59zmHRr6HnzicLULHmMHmVSbRTezVH99i.png"],"tags":["dev","python","tribes","archon","proofofbrain","pimp"],"users":[]}
created2025-06-18 15:33:54
last_update2025-06-18 15:35:39
depth0
children4
last_payout1969-12-31 23:59:59
cashout_time2025-06-25 15:33:54
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value10.234 HBD
promoted0.000 HBD
body_length5,113
author_reputation78,856,670,221,142
root_title"Fun with Python: Converting To and From Roman Numerals"
beneficiaries
0.
accountthecrazygm.bank
weight1,500
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id143,428,889
net_rshares35,650,282,664,513
author_curate_reward""
vote details (194)
@ecoinstant ·
$0.02
All the vibe coders should also learn to code with great content like this! We can make a collextion later.

!PAKX
!PIMP
!PIZZA
👍  
properties (23)
authorecoinstant
permlinkre-thecrazygm-sy287o
categoryhive-186392
json_metadata{"tags":["hive-186392"],"app":"peakd/2025.6.1","image":[],"users":[]}
created2025-06-18 16:23:51
last_update2025-06-18 16:23:51
depth1
children1
last_payout1969-12-31 23:59:59
cashout_time2025-06-25 16:23:51
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value0.019 HBD
promoted0.000 HBD
body_length127
author_reputation827,966,936,985,061
root_title"Fun with Python: Converting To and From Roman Numerals"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id143,430,045
net_rshares64,871,363,649
author_curate_reward""
vote details (1)
@pakx ·
<center><table><tr></tr><tr><td><center><img src='https://files.peakd.com/file/peakd-hive/pakx/PakX-logo-transparent.png'><p><sup><a href='https://hive-engine.com/?p=market&t=PAKX'>View or trade </a> <code>PAKX</code> tokens.</sup></p></center></td><td><center>@ecoinstant, PAKX has voted the post by @thecrazygm. (2/2 calls)</p><br><br><p>Use !PAKX command if you hold enough balance to call for a @pakx vote on worthy posts! More details available on <a href='/@pakx'>PAKX Blog</a>.</p></center></td></tr></table></center>
properties (22)
authorpakx
permlinkre-ecoinstant-1750263879
categoryhive-186392
json_metadata"{"tags": ["pakx", "hivepakistan"], "app": "HiveDiscoMod"}"
created2025-06-18 16:24:42
last_update2025-06-18 16:24:42
depth2
children0
last_payout1969-12-31 23:59:59
cashout_time2025-06-25 16:24:42
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length524
author_reputation32,485,921,861
root_title"Fun with Python: Converting To and From Roman Numerals"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id143,430,075
net_rshares0
@pizzabot ·
<center>PIZZA!


$PIZZA slices delivered:
@ecoinstant<sub>(2/20)</sub> tipped @thecrazygm 


<sub>Come get [MOON](https://moon.hive.pizza)ed!</sub></center>
properties (22)
authorpizzabot
permlinkre-fun-with-python-converting-to-and-from-roman-numerals-20250618t162412z
categoryhive-186392
json_metadata"{"app": "pizzabot"}"
created2025-06-18 16:24:12
last_update2025-06-18 16:24:12
depth1
children0
last_payout1969-12-31 23:59:59
cashout_time2025-06-25 16:24:12
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length156
author_reputation7,439,344,392,562
root_title"Fun with Python: Converting To and From Roman Numerals"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id143,430,057
net_rshares0
@tydynrain ·
$0.17
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! 😁 🙏 💚 ✨ 🤙 
👍  , , , , , , , , , ,
properties (23)
authortydynrain
permlinkre-thecrazygm-2025618t195814502z
categoryhive-186392
json_metadata{"tags":["dev","python","tribes","archon","proofofbrain","pimp"],"app":"ecency/3.2.0-vision","format":"markdown+html"}
created2025-06-19 05:58:18
last_update2025-06-19 05:58:18
depth1
children0
last_payout1969-12-31 23:59:59
cashout_time2025-06-26 05:58:18
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value0.166 HBD
promoted0.000 HBD
body_length194
author_reputation199,086,027,820,281
root_title"Fun with Python: Converting To and From Roman Numerals"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id143,440,893
net_rshares575,730,700,622
author_curate_reward""
vote details (11)