Skip to content

How to Convert Integers to Roman Numerals and Vice Versa in Python

Learn how to implement efficient Python functions for converting integers to Roman numerals (1-3999) using a greedy algorithm and parsing Roman numerals back to integers with reverse iteration.

Roman numerals are a numeric system originating in ancient Rome, using combinations of letters from the Latin alphabet to signify values. While modern computing uses the decimal system, converting integers to Roman numerals is a common task in software development for copyright dates, stylized lists, or generation of outlines.

This guide explains the logic behind Roman numeral composition and provides efficient Python solutions for converting integers to Roman numerals and vice versa.

Understanding Roman Numeral Logic

Standard Roman numerals use seven basic symbols. However, the system relies on a combination of addition and subtraction rules.

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

The Subtraction Rule

To avoid four consecutive characters (like "IIII"), smaller values are placed before larger values to indicate subtraction. To write an efficient algorithm, we treat these special pairs as distinct "values."

  • IV (4) and IX (9)
  • XL (40) and XC (90)
  • CD (400) and CM (900)

Method 1: Integer to Roman (Greedy Algorithm)

The most efficient way to convert an integer to a Roman numeral is the Greedy Algorithm. We iterate through the values from largest to smallest, subtracting the value from the input number and appending the corresponding symbol until the number reaches zero.

def int_to_roman(num):
    val_map = [
        (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')
    ]
    
    roman_numeral = ''
    
    for value, symbol in val_map:
        while num >= value:
            roman_numeral += symbol
            num -= value
            
    return roman_numeral

print(f"1994 -> {int_to_roman(1994)}")
print(f"58   -> {int_to_roman(58)}")
print(f"4    -> {int_to_roman(4)}")

Output:

1994 -> MCMXCIV
58   -> LVIII
4    -> IV

Method 2: Roman to Integer (Reverse Parsing)

To convert back from Roman numerals to an integer, we scan the string. A clever trick is to iterate in reverse. This allows us to maintain a running total where we add the value if it is greater than or equal to the previous value seen, or subtract it if it is smaller.

def roman_to_int(s):
    roman_values = {
        'I': 1, 'V': 5, 'X': 10,
        'L': 50, 'C': 100, 'D': 500, 'M': 1000
    }
    
    total = 0
    prev_value = 0
    
    for char in reversed(s.upper()):
        current_value = roman_values.get(char, 0)
        
        if current_value >= prev_value:
            total += current_value
        else:
            total -= current_value
            
        prev_value = current_value
        
    return total

print(f"MCMXCIV -> {roman_to_int('MCMXCIV')}")
print(f"IV      -> {roman_to_int('IV')}")

Output:

MCMXCIV -> 1994
IV      -> 4

Handling Errors and Edge Cases

Standard Roman numerals are typically defined for the range 1 to 3999. Zero and negative numbers were not part of the original system, and standard notation breaks down above 3999 (requiring lines over letters to indicate multiplication by 1000).

You should implement validation to ensure inputs adhere to these constraints.

def int_to_roman(num):
    val_map = [
        (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')
    ]
    
    roman_numeral = ''
    
    for value, symbol in val_map:
        while num >= value:
            roman_numeral += symbol
            num -= value
            
    return roman_numeral

def safe_int_to_roman(num):
    if not isinstance(num, int):
        raise TypeError("Input must be an integer.")
    if not (0 < num < 4000):
        raise ValueError("Number must be between 1 and 3999.")

    return int_to_roman(num)

try:
    print(safe_int_to_roman(2023))
except ValueError as e:
    print(e)

try:
    print(safe_int_to_roman(0))
except ValueError as e:
    print(f"Error: {e}")

Output:

MMXXIII
Error: Number must be between 1 and 3999.

Conclusion

Converting integers to Roman numerals is a classic algorithmic problem solved efficiently using a greedy approach with a lookup list containing subtraction pairs.

  1. Define Mappings: Create a list of tuples for values and symbols, including special cases like 900 (CM) and 4 (IV).
  2. Iterate Descending: Process the largest values first.
  3. Validate Inputs: Ensure the integer is within the standard range (1-3999).
  4. Reverse for Decoding: When converting back to integers, processing the string from right to left simplifies the logic for subtraction rules.
Navigation