with: Erik Oosterwal
![]()
Custom Search
|
I = |
1 |
V = |
5 |
X = |
10 |
L = |
50 |
C = |
100 |
D = |
500 |
M = |
1000 |
The basic rule states that numerals are read from left to right, larger valued
letters precede smaller valued letters, and multiple letters in a row represent
multiples of that value. For instance:
I = 1
II = 2
III = 3
VIII = 8
CVIII = 108
CCVIII = 208
DCCVIII = 708
MDCCVIII = 1708
The single exception to the basic rule is that if a larger value letter is
preceded by a single smaller value letter, then the combination represents
the value of the larger value letter minus the value of the smaller value
letter. This exception only holds true for the values 4, 9, 40, 90,
400, and 900, which are written as IV, IX, XL, XC, CD, and CM respectively.
A seldom used convention of Roman numerals is that you can create values larger values by overscoring letters to represent the letter's normal value multiplied by 1000:
_ |
|
_ |
|
_ |
|
_ |
|
_ |
|
_ |
|
We won't bother with the larger values in this routine and limit our numbers
to values between 1 and 3999.
DECIMAL to ROMAN Algorithm 1
The following pseudo code should provide enough information to implement
the algorithm in your language of choice:
function roman(InputValue) as string dim InputValue as integer ' Declare Variables dim RomanValue as string if (InputValue > 3999) OR (InputValue < 1) roman = "N/A" return endif RomanValue = "" While InputValue > 999 ( RomanValue = RomanValue + "M" ' Concatenate the letters to the right side InputValue = InputValue - 1000 ' Reduce the amount left in InputValue ) If InputValue > 899 ( RomanValue = RomanValue + "CM" ' Concatenate letters to the right side InputValue = InputValue - 900 ) If InputValue > 499 ( RomanValue = RomanValue + "D" InputValue = InputValue - 500 ) If InputValue > 399 ( RomanValue = RomanValue + "CD" InputValue = InputValue - 400 ) While InputValue > 99 ( RomanValue = RomanValue + "C" InputValue = InputValue - 100 ) If InputValue > 89 ( RomanValue = RomanValue + "XC" InputValue = InputValue - 90 ) If InputValue > 49 ( RomanValue = RomanValue + "L" InputValue = InputValue - 50 ) If InputValue > 39 ( RomanValue = RomanValue + "XL" InputValue = InputValue - 40 ) While InputValue > 9 ( RomanValue = RomanValue + "X" InputValue = InputValue - 10 ) If InputValue > 8 ( RomanValue = RomanValue + "IX" InputValue = InputValue - 9 ) If InputValue > 4 ( RomanValue = RomanValue + "V" InputValue = InputValue - 5 ) If InputValue > 3 ( RomanValue = RomanValue + "IV" InputValue = InputValue - 4 ) While InputValue > 0 ( RomanValue = RomanValue + "I" InputValue = InputValue - 1 ) roman = RomanValue return
DECIMAL to ROMAN Algorithm 2
I've come up with a shorter algorithm (1-18-2008) that should be easy enough to understand. This new algorithm does away with all the cumbersome IF statements from the previous algorithm and wraps it all up into one WHILE loop. It's still the same principle as that used in the previous algorithm, but instead of hard coding all the values of the Roman Numeral characters and pairs into separate IF and WHILE clauses, the values and characters are stored in a couple of arrays and referenced in a single WHILE loop. I don't know if this new code is faster than the previous algorithm; I'll have to code them both and do some tests--that can wait for another day. For now, here's the updated code written in VB .NET:
Private Sub cmdConvert_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdConvert.Click ' Declare and initialize all variables. Roman Numerals and their standard combinations ' are stored in strRoman. The corresponding values are stored in intDecimal. Dim strRoman() As String = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"} Dim intDecimal() As Integer = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1} Dim intInput As Integer = Nothing ' Numeric representation of the input field. Dim intTemp As Integer = Nothing ' Used to store the magnitude of each decimal digit. Dim intPointer As Short = 0 ' Pointer to the Roman and Decimal arrays, starts at the first cell. Dim intCountI As Short = Nothing ' Temporary variable used for loops. intInput = CInt(txtInput.Text) ' Convert the string in the input field to an integer. txtOutput.Clear() ' Clear the contents of the output field. While intInput > 0 ' Check to see if intInput still has any value left in it. intTemp = intInput \ intDecimal(intPointer) ' See how many of the currently selected value can fit in the remaining input. For intCountI = 1 To intTemp txtOutput.Text += strRoman(intPointer) ' Append a number of Roman Characters depending on the value of intTemp. Next intInput -= intTemp * intDecimal(intPointer) ' Subtract the value of the characters that were appended to output from the input. intPointer += 1 ' Move the pointer to the next cell of the arrays. End While End Sub
You can download a ZIP file with all the VB .NET project files and an executable here.
DECIMAL to ROMAN Algorithm 3 - MCXI
It ocurred to me, after designing and implementing the previous algorithm, that if you're willing to sacrifice a bit more memory space for a table, then there's a faster way to convert from Decimal to Roman Numeral notation. The following code shows an implementation of the algorithm in VB .NET. I call it the MCXI algorithm due to the values of the four rows in the table. There are some funny things going on between the 0 based indexing used in accessing array data and 1 based indexing used in the MID function used for parsing the input string. You may need to address this if you use a different programming language.
Private Sub btnConvert_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnConvert.Click ' Algorithm MCXI ' Written by: Erik Oosterwal ' Written on: 1-21-2008 ' ' Routine to convert Decimal values to Roman Numeral notation. This algorithm uses a table to store ' all the standard Roman Numeral notation for each digit in each Base-10 digit position (ones, tens, ' hundreds, thousands). Each Decimal character is then evaluated to find its magnitude and extract ' the corresponding Roman Numeral notation for that value and prepend it to the output string. ' ' This routine does not check for proper usage - the input is not checked for valid numeral characters ' and it does not check to verify that the values are positive values between 1 and 4000. To make ' function robust, you check put checks in place for these conditions. 'Store the Roman codes for each Decimal digit. Dim strRoman(,) As String = {{"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"}, _ {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"}, _ {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"}, _ {"", "M", "MM", "MMM", "MMMM", "", "", "", "", ""}} Dim strOutput As String = Nothing ' Initialize the output string to blank. Dim intLoopI As Integer = Nothing ' Counter used for the Loop Dim intStrLength As Integer = Nothing ' The length of the input string. intStrLength = Len(Me.txtInput.Text) ' Find out how many digits are in the input number (magnitude). For intLoopI = intStrLength To 1 Step -1 ' MID function uses 1 based indexing for the string pointer. strOutput = strRoman(intStrLength - intLoopI, Val(Mid$(Me.txtInput.Text, intLoopI, 1))) & strOutput Next Me.txtOutput.Text = strOutput End Sub
As you can see, algorithm number three (MCXI Algorithm) executes the loop a maximum of 4 times and uses no division or multiplication. This should speed up the run time a great deal over the previous two listings.
You can download an executable along with all the VB .NET project files for this algorithm here.
ROMAN NUMERAL to DECIMAL Algorithm 1
Ok, I've promised a way to convert Roman Numeral back to Decimals for a very long time, and I'm finally getting around to showing one method of doing that.
Start at the left-most character in the Roman Numeral string and work your way to the last character one character at a time. Add the value of the current character to the total. If the current character has a larger value than the previous character, then subtract 2x the value of the previous character from the total.
For instance, XIX is equal to 19. Start at the first character and add 10 to the total; the total is now 10. Go to the second character and add 1 to the total; the total is now 11. Go to the third character and add 10 to the total; the total is now 21, but... Because the current character has a larger value than the previous character we subtract 2x the value of the previous character, 1, from the total; the total is now 19. There are no more characters so we stop.
Function Roman2Decimal(strInput as string) as integer Dim intValues() as integer ' Reserve space for an array. Dim intLoopI as integer = nothing ' Initialize the loop counter to 0. Dim intTotal as integer = nothing ' Clear the total. Dim intPreviousPointer as integer = 99999 ' Set the value of the previous pointer to some MAX value. Dim intCurrentPointer as integer = nothing ' This will hold the value of the current Roman Numeral character. intValues(M) = 1000 ' This array addressing method intValues(D) = 500 ' assumes that the language you intValues(C) = 100 ' are using allows you to use intValues(L) = 50 ' values other than integer values intValues(X) = 10 ' to access data in an array. intValues(V) = 5 intValues(I) = 1 For intLoopI = 0 to (len(strInput)-1) ' Assuming 0 indexing... intCurrentPointer = intValues(mid$(strInput,intLoopI,1)) ' Get the value of the current Roman Numeral character intTotal = intTotal + intCurrentPointer ' Add the value to the total. If intCurrentPointer > intPreviousPointer then ' If the current value is larger than the previous, then intTotal = intTotal - (2 * intPreviousPointer) ' subtract twice the value of the previous character from the total. EndIf intPreviousPointer = intCurrentPointer ' Move the value of the current character to be the ' value of the previous character and get then next one. Next Roman2Decimal = intTotal End Function
There is one small glitch with this algorithm--the rules for writing Roman Numerals does not allow for three characters in a row of increasing value, like IXC. This number is a bit ambiguous... ...Does it represent the value 99 (IX=9 + XC=90 = 99), or 91 (IX=9, and 9 less than C=100 is 91), or 89, as this algorothm produces (add 1 for I (1), then add 10 for X (11), but subtract 2 because it's a larger character (9), then add 100 for C (109), but subtract 20 because it's a larger character (89)). You can also look at it this way... whenever you have a larger value following a smaller value, then it's like starting with the right most of the characters and subtracting the value of the character to its immediate left. IX = 10-1 = 9, XC = 100-10 = 90, or a bit unconventionally, IC = 100-1 = 99, or even IM = 1000-1 = 999. If you follow this rule, then IXC would be 100-10-1 or 89, which is just what this algorithm provides. Incidentally, this algorithm can also handle the other cases, IC, IM, etc.
I'll get this algorithm coded and verify everything's kosher and provide some VB or VB. NET project files for your use as soon as possible.
ROMAN NUMERAL to DECIMAL Algorithm 2
Here's a second algorithm for converting Roman Numeral notation to a Decimal value:
Start at the right-most character. As long as the value of current character is the same size or larger than then previous character, then add that value to the total. If the value of the current character is smaller than the previous character then subtract that value from the total. This algorithm produces the same output as the first.
Here is a VB .NET implementation of the algorithm:
Private Sub btnConvert_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnConvert.Click ' Roman2Decimal - 2 ' ' Written by: Erik Oosterwal ' Written on: 1 - 21 - 2008 ' ' Working your way from the least significant Roman Numeral (right side of the string) ' to the most significant, when you encounter a value higher than the previous ' value, add that value to the total. when you encounter a smaller value, subtract ' that value from the total. ' ' Some programming languages, such as PHP, allow you to use strings as keys to an array. ' Using a language like that will let you remove all the ASC() references, and also ' reduce the size of the intValues array to 7 cells. Dim intValues(100) As Integer ' Reserve space for an array. Dim intLoopI As Integer = Nothing ' Initialize the loop counter to 0. Dim intTotal As Integer = Nothing ' Clear the total. Dim intPreviousPointer As Integer = 0 ' Set the value of the previous pointer to some MIN value. Dim intCurrentPointer As Integer = Nothing ' This will hold the value of the current Roman Numeral character. intValues(Asc("M")) = 1000 intValues(Asc("D")) = 500 intValues(Asc("C")) = 100 intValues(Asc("L")) = 50 intValues(Asc("X")) = 10 intValues(Asc("V")) = 5 intValues(Asc("I")) = 1 For intLoopI = Len(Me.txtInput.Text) To 1 Step -1 ' Assume 1 indexing intCurrentPointer = intValues(Asc(Mid$(Me.txtInput.Text, intLoopI, 1).ToUpper)) If intCurrentPointer < intPreviousPointer Then intTotal = intTotal - intCurrentPointer Else intTotal = intTotal + intCurrentPointer End If intPreviousPointer = intCurrentPointer Next Me.txtOutput.Text = Str(intTotal) End Sub
There's a little less math involved in the second algorithm, which means that it should execute a bit faster. In the first Roman 2 Decimal algorithm, every instance of the right-character being larger than the left-character (IV, IX, XC, etc) resulted in the total being acted upon twice. With this latter algorithm, the total is acted upon only once for each character in the input string no matter if it's larger or smaller than the previous one.
You can download an executable along with the VB .NET project in a zip file here.
I'll get all the algorithms described on this page coded as soon as time permits and provide a comparison of the relative speed of each.