Title: Convert to and from Roman numerals in C#
For a nice discussion of Roman numerals, see Roman Numerals, How they work. That page explains the basic rules and more advanced rules such as using parentheses or an overline to multiply values by 1,000. It includes a lot of interesting historical notes that leave you feeling that the Romans basically made up and broke the rules freely and they went along. The story about Sulpicius Galba's inheritance is particularly interesting.
This example includes methods that convert to and from Roman numerals. It follows most of the rules you probably know from elementary school. The only unusual issue is that it uses parentheses to multiply by 1,000. For example, in the value (IV)CCCXXI the parentheses mean the IV part should be multiplied by 1,000 giving 4,000, so the total value is 4321.
Roman to Arabic
The program uses the following code to convert a string containing Roman numerals into an integer.
// Maps letters to numbers.
private Dictionary<char, int> CharValues = null;
// Convert Roman numerals to an integer.
private int RomanToArabic(string roman)
{
// Initialize the letter map.
if (CharValues == null)
{
CharValues = new Dictionary<char, int>();
CharValues.Add('I', 1);
CharValues.Add('V', 5);
CharValues.Add('X', 10);
CharValues.Add('L', 50);
CharValues.Add('C', 100);
CharValues.Add('D', 500);
CharValues.Add('M', 1000);
}
if (roman.Length == 0) return 0;
roman = roman.ToUpper();
// See if the number begins with (.
if (roman[0] == '(')
{
// Find the closing parenthesis.
int pos = roman.LastIndexOf(')');
// Get the value inside the parentheses.
string part1 = roman.Substring(1, pos  1);
string part2 = roman.Substring(pos + 1);
return 1000 * RomanToArabic(part1) + RomanToArabic(part2);
}
// The number doesn't begin with (.
// Convert the letters' values.
int total = 0;
int last_value = 0;
for (int i = roman.Length  1; i >= 0; i)
{
int new_value = CharValues[roman[i]];
// See if we should add or subtract.
if (new_value < last_value)
total = new_value;
else
{
total += new_value;
last_value = new_value;
}
}
// Return the result.
return total;
}
The CharValues dictionary stores the values of the basic Roman numerals: I = 1, V = 5, etc. When the RomanToArabic method starts, it initializes CharValues if it has not been initialized already.
Next if the input string begins with an open parenthesis, the method finds the corresponding closing parenthesis and separates the string into the piece inside the parentheses and the piece that comes after the closing parenthesis. For example, it breaks the input string (IV)CCCXXI into the pieces IV and CCCXXI. The method then calls itself recursively to evaluate the two pieces, multiples the first by 1,000, and adds them together.
If the input doesn't begin with an open parenthesis, the code loops through the letters from righttoleft. For each letter, it uses the CharValues dictionary to get the letter's value. If that value is smaller than the value of the previous letter, it subtracts the value from the total. For example, if the input contains IV, it subtracts the I.
If the letter's value is not smaller than the previous value, it adds the value. For example, if the input contains IV, it adds the V.
When it has finished reading all of the letters, the method returns the result.
Arabic to Roman
The program uses the following code to convert an integer into Roman numerals.
// Map digits to letters.
private string[] ThouLetters = { "", "M", "MM", "MMM" };
private string[] HundLetters =
{ "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" };
private string[] TensLetters =
{ "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" };
private string[] OnesLetters =
{ "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" };
// Convert Roman numerals to an integer.
private string ArabicToRoman(int arabic)
{
// See if it's >= 4000.
if (arabic >= 4000)
{
// Use parentheses.
int thou = arabic / 1000;
arabic %= 1000;
return "(" + ArabicToRoman(thou) + ")" +
ArabicToRoman(arabic);
}
// Otherwise process the letters.
string result = "";
// Pull out thousands.
int num;
num = arabic / 1000;
result += ThouLetters[num];
arabic %= 1000;
// Handle hundreds.
num = arabic / 100;
result += HundLetters[num];
arabic %= 100;
// Handle tens.
num = arabic / 10;
result += TensLetters[num];
arabic %= 10;
// Handle ones.
result += OnesLetters[arabic];
return result;
}
The string arrays ThouLetters, HundLetters, and so on hold the Roman numeral representations for various Arabic digits. For example, HundLetters[3] contains the Roman numeral representation for 300. Notice that the 0 entry in all of the arrays is an empty string because the Romans had no notation for 0.
The ArabicToRoman method first checks whether the input value is greater than or equal to 4,000, in which case the method will use parentheses to multiply a value by 1,000. In this case the code separates the value into two pieces: The original value divided by 1,000 and the remainder. For example, if the input is 54,321, the code separates it into the pieces 54 and 321. The method calls itself recursively to convert the pieces into Roman numerals, adds parentheses around the first piece's representation, and returns the combined results. In this example, it would return (54)321 with the two pieces converted into Roman numerals, or (LIV)CCCXXI.
If the input value is less than 4,000, the method pulls out the value's thousands. It calculates the number of thousands in the value, uses that number as an index into the ThouLetters array, and adds the corresponding array value to the result string.
The method repeats the same steps for the value's hundreds, tens, and ones digits. When it finishes, it simply returns the result.
Conclusion
When you enter a value in either of the two upper text boxes and click Convert, the program converts your value into the other format and then converts the result back into the original format. For example, if you enter 1984 in the Arabic text box, the program converts that value into the Roman numerals MCMLXXXIV and then converts that value back into 1984.
The program works quite well for the most obvious cases and handles many odd cases. For example, if you enter the Roman numerals XIIV, the program produces the Arabic value 13 and then converts that into the Roman numerals XIII.
There are some really weird inputs that the program doesn't handle. For example, it can't understand (M)(V) even though a person could probably figure out that this means 15,000.
Download the example to experiment with it and to see additional details.
