1. Introduction

The ancient Romans developed their own numeric system called Roman numerals. The system uses letters with different values to represent numbers. Roman numerals are still used today in some minor applications.

In this tutorial, we’ll implement simple converters that will transform numbers from one system to the other.

2. Roman Numerals

In the Roman system, we have 7 symbols that represent numbers:

  • I represents 1
  • V represents 5
  • X represents 10
  • L represents 50
  • C represents 100
  • D represents 500
  • M represents 1000

Originally, people used to represent a 4 with IIII or 40 with XXXX. This can be quite uncomfortable to read. It’s also easy to mistake four symbols next to each other for three symbols.

Roman numerals use subtractive notation to avoid such mistakes. Instead of saying four times one (IIII), one can say that it’s one less than five (IV).

How’s it important from our perspective? It’s important because instead of simply adding numbers symbol by symbol, we might need to check the next symbol to determine if the number should be added or subtracted.

3. Model

Let’s define an enum to represent the Roman Numerals:

enum RomanNumeral {
    I(1), IV(4), V(5), IX(9), X(10), 
    XL(40), L(50), XC(90), C(100), 
    CD(400), D(500), CM(900), M(1000);

    private int value;

    RomanNumeral(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
    
    public static List<RomanNumeral> getReverseSortedValues() {
        return Arrays.stream(values())
          .sorted(Comparator.comparing((RomanNumeral e) -> e.value).reversed())
          .collect(Collectors.toList());
    }
}

Notice that we’ve defined additional symbols to help out with subtractive notation. We have also defined an additional method named getReverseSortedValues().

This method will allow us to explicitly retrieve the defined Roman numerals in descending value order.

4. Roman to Arabic

Roman numerals can only represent integers between 1 to 4000. We can use the following algorithm to convert a Roman numeral to an Arabic number (iterating through symbols in reverse order from M to I):

LET numeral be the input String representing an Roman Numeral
LET symbol be initialy set to RomanNumeral.values()[0]
WHILE numeral.length > 0:
    IF numeral starts with symbol's name:
        add symbol's value to the result
        remove the symbol's name from the numeral's beginning
    ELSE:
        set symbol to the next symbol

4.1. Implementation

Next, we can implement the algorithm in Java:

public static int romanToArabic(String input) {
    String romanNumeral = input.toUpperCase();
    int result = 0;
        
    List<RomanNumeral> romanNumerals = RomanNumeral.getReverseSortedValues();

    int i = 0;

    while ((romanNumeral.length() > 0) && (i < romanNumerals.size())) {
        RomanNumeral symbol = romanNumerals.get(i);
        if (romanNumeral.startsWith(symbol.name())) {
            result += symbol.getValue();
            romanNumeral = romanNumeral.substring(symbol.name().length());
        } else {
            i++;
        }
    }

    if (romanNumeral.length() > 0) {
        throw new IllegalArgumentException(input + " cannot be converted to a Roman Numeral");
    }

    return result;
}

4.2. Test

Finally, we can test the implementation:

@Test
void given2018Roman_WhenConvertingToArabic_ThenReturn2018() {
    String roman2018 = "MMXVIII";

    int result = RomanArabicConverter.romanToArabic(roman2018);

    assertThat(result).isEqualTo(2018);
}

5. Arabic to Roman

We can use the following algorithm to convert from Arabic to Roman numerals (iterating through symbols in reverse order from M to I):

LET number be an integer between 1 and 4000
LET symbol be RomanNumeral.values()[0]
LET result be an empty String
WHILE number > 0:
    IF symbol's value <= number:
        append the result with the symbol's name
        subtract symbol's value from number
    ELSE:
        pick the next symbol

5.1. Implementation

Next, we can now implement the algorithm:

public static String arabicToRoman(int number) {
    if ((number <= 0) || (number > 4000)) {
        throw new IllegalArgumentException(number + " is not in range (0,4000]");
    }

    List<RomanNumeral> romanNumerals = RomanNumeral.getReverseSortedValues();

    int i = 0;
    StringBuilder sb = new StringBuilder();

    while ((number > 0) && (i < romanNumerals.size())) {
        RomanNumeral currentSymbol = romanNumerals.get(i);
        if (currentSymbol.getValue() <= number) {
            sb.append(currentSymbol.name());
            number -= currentSymbol.getValue();
        } else {
            i++;
        }
    }

    return sb.toString();
}

5.2. Test

Finally, we can test the implementation:

@Test
void given1999Arabic_WhenConvertingToRoman_ThenReturnMCMXCIX() {
    int arabic1999 = 1999;

    String result = RomanArabicConverter.arabicToRoman(arabic1999);

    assertThat(result).isEqualTo("MCMXCIX");
}

6. Conclusion

In this quick article, we’ve shown how to convert between Roman and Arabic numerals.

We have used an enum to represent the set of Roman numerals and we have created a utility class to perform the conversions.

The complete implementation and all tests can be found over on GitHub.