Showing posts with label roman numerals. Show all posts
Showing posts with label roman numerals. Show all posts

Sunday, September 5, 2010

Roman Numerals, Part 4

Keith Alexander of Albuquerque, New Mexico writes:
“I like your Roman Numeral library. I needed a function to test for Roman numerals, so I wrote this one.
  1. // Check to see if the string is a Roman Numeral
  2. // NOTE: this doesn't check for fractions, overbars, the Bede "N" (zero) etc.
  3. // NOTE: It also doesn't check for a well-formed Roman Numeral.
  4. function is_roman_numeral( $roman )
  5. {
  6.     // Strip every non-word character
  7.     // - A-Z, 0-9, apostrophe and understcore are what's left
  8.     $roman = preg_replace( "/[^A-Z0-9_']/iu", "", $roman );
  9.     // if it contains anything other than MDCLXVI, then it's not a Roman Numeral
  10.     $result = preg_match( "/[^MDCLXVI]/u", $roman );
  11.     if( $result )
  12.     {
  13.         return FALSE;
  14.     }
  15.     return TRUE;
  16. }

Who knows if blogger is going to show it properly. If not, just contact me and I'll send it you in email or something. Anyway, it's something I wrote in 5 minutes. If you want to add it to your library, modified or otherwise, please feel free.”

Thanks for writing in Keith, and sorry for the late response. There are two ways to validate a Roman number, using regular expressions like you did, and converting back to an Arabic number (if the conversion fails, it's not a Roman number).

I'm not sure about using a regular expression to remove non-word characters. My gut tells me that anything containing such characters should fail validation as a Roman number. Also, I would reverse the match and eliminate the if statement by directly returning the result of the match.

PHP

  1. function isRoman($roman)
  2. {
  3.     return preg_match("/[MDCLXVI]/u", $roman);
  4. }

ASP

  1. function isRoman(roman)
  2.     dim regEx
  3.     set regEx = new RegExp
  4.     with regEx
  5.         .IgnoreCase = true
  6.         .Global = true
  7.         .Pattern = "[MDCLXVI]"
  8.     end with
  9.     if regEx.Test(roman) then
  10.         isRoman = true
  11.     else
  12.         isRoman = false
  13.     end if
  14.     set regEx = nothing
  15. end function


Saturday, August 30, 2008

Roman Numerals, Part 3

Back in March, we wrote a function to turn an arabic number into a Roman numeral. That function had a limitation of numbers smaller than 5000. There are ways of writing Roman numerals larger than 5000, but they are not as well accepted by purists because they evolved during later time periods. Out of respect for the purists, this new function will hinge on the old one, rather than replace it.

In the system we will be using, a bar is placed over the numeral to indicate that it is multipled by 1000:

  • V = 5000
  • X = 10,000
  • L = 50,000
  • C = 100,000
  • D = 500,000
  • M = 1 million

Since there are no Unicode characters for this purpose, we will have to cheat a little bit by using some HTML and CSS.


ASP

  1. function bigroman(ByVal arabic)
  2.     dim thousands
  3.     thousands = Array("", "M", "MM", "MMM", "M(V)", "(V)", "(V)M", "(V)MM", "(V)MMM", "M(X)")
  4.     if arabic >= 10000 then
  5.         bigroman = "(" & roman((arabic - (arabic mod 10000)) / 1000) & ")"
  6.         arabic = arabic mod 10000
  7.     end if
  8.     bigroman = bigroman & thousands((arabic - (arabic mod 1000)) / 1000)
  9.     arabic = arabic mod 1000
  10.     bigroman = bigroman & roman(arabic)
  11.     ' Convert parentheses to <span> tags.
  12.     bigroman = replace(bigroman, "(", "<span style=""text-decoration: overline"">")
  13.     bigroman = replace(bigroman, ")", "</span>")
  14. end function

PHP

  1. function bigroman($arabic)
  2. {
  3.     $thousands = Array("", "M", "MM", "MMM", "M(V)", "(V)", "(V)M", "(V)MM", "(V)MMM", "M(X)");
  4.     if ($arabic >= 10000)
  5.     {
  6.         $bigroman = "(" . roman(($arabic - fmod($arabic, 10000)) / 1000) . ")";
  7.         $arabic = fmod($arabic, 10000);
  8.     }
  9.     $bigroman .= $thousands[($arabic - fmod($arabic, 1000)) / 1000];
  10.     $arabic = fmod($arabic, 1000);
  11.     $bigroman .= roman($arabic);
  12.     // Convert parentheses to <span> tags.
  13.     $bigroman = str_replace("(", "<span style=""text-decoration: overline"">", $bigroman);
  14.     $bigroman = str_replace(")", "</span>", $bigroman);
  15.     return $bigroman;
  16. }

Saturday, March 15, 2008

Roman Numerals, Part 2

Last week we converted integers into Roman numerals, and I promised this week we would write a function to do the opposite. Part of the difficulty in working with Roman numerals is the subtractive notation. The number four can be written as IV or IIII; writing it as IV is the subtractive notation. When I precedes V, it means one less than five; if you reverse the two (VI), it means one more than five.


To make our life easier, we want to expand the subtractive notation so that each numeral is equal to its value. While we're at it, we'll also write a function to compress Roman numerals back in subtractive notation.


ASP

  1. function roman_expand(ByVal roman)
  2.     roman = replace(roman, "CM", "DCCCC")
  3.     roman = replace(roman, "CD", "CCCC")
  4.     roman = replace(roman, "XC", "LXXXX")
  5.     roman = replace(roman, "XL", "XXXX")
  6.     roman = replace(roman, "IX", "VIIII")
  7.     roman = replace(roman, "IV", "IIII")
  8.     roman_expand = roman
  9. end function
  10. function roman_compress(ByVal roman)
  11.     roman = replace(roman, "DCCCC", "CM")
  12.     roman = replace(roman, "CCCC", "CD")
  13.     roman = replace(roman, "LXXXX", "XC")
  14.     roman = replace(roman, "XXXX", "XL")
  15.     roman = replace(roman, "VIIII", "IX")
  16.     roman = replace(roman, "IIII", "IV")
  17.     roman_compress = roman
  18. end function

PHP

  1. function roman_expand($roman)
  2. {
  3.     $roman = str_replace("CM", "DCCCC", $roman);
  4.     $roman = str_replace("CD", "CCCC", $roman);
  5.     $roman = str_replace("XC", "LXXXX", $roman);
  6.     $roman = str_replace("XL", "XXXX", $roman);
  7.     $roman = str_replace("IX", "VIIII", $roman);
  8.     $roman = str_replace("IV", "IIII", $roman);
  9.     return $roman;
  10. }
  11. function roman_compress($roman)
  12. {
  13.     $roman = str_replace("DCCCC", "CM", $roman);
  14.     $roman = str_replace("CCCC", "CD", $roman);
  15.     $roman = str_replace("LXXXX", "XC", $roman);
  16.     $roman = str_replace("XXXX", "XL", $roman);
  17.     $roman = str_replace("VIIII", "IX", $roman);
  18.     $roman = str_replace("IIII", "IV", $roman);
  19.     return $roman;
  20. }

These expand and compress functions would be essential if you wanted to write some functions to actually perform arithmetic on Roman numerals, and although on paper Roman addition is fairly straightforward, an implementation of it in code performs more poorly than simply converting the Roman numeral back into an integer and using the built-in math operators.


The following ASP function requires the InstrCount function from my previous post.


ASP

  1. function arabic(ByVal roman)
  2.     Dim result
  3.     result = 0
  4.     ' Remove subtractive notation.
  5.     roman = roman_expand(roman)
  6.     ' Calculate for each numeral.
  7.     result = result + InstrCount(roman, "M") * 1000
  8.     result = result + InstrCount(roman, "D") * 500
  9.     result = result + InstrCount(roman, "C") * 100
  10.     result = result + InstrCount(roman, "L") * 50
  11.     result = result + InstrCount(roman, "X") * 10
  12.     result = result + InstrCount(roman, "V") * 5
  13.     result = result + InstrCount(roman, "I")
  14. end function

PHP

  1. function arabic($roman)
  2. {
  3.     $result = 0;
  4.     // Remove subtractive notation.
  5.     $roman = roman_expand($roman);
  6.     // Calculate for each numeral.
  7.     $result += substr_count($roman, 'M') * 1000;
  8.     $result += substr_count($roman, 'D') * 500;
  9.     $result += substr_count($roman, 'C') * 100;
  10.     $result += substr_count($roman, 'L') * 50;
  11.     $result += substr_count($roman, 'X') * 10;
  12.     $result += substr_count($roman, 'V') * 5;
  13.     $result += substr_count($roman, 'I');
  14.     return $result;
  15. }

Although I don't have code for you to actually add two Roman numerals together, I will tell you how to do it. For this example, we will add the numerals XIV and VII. First we must remove the subtractive notation, so XIV becomes XIIII and VII stays as it is. On paper we would then concatenate the two together and sort them.


XIIII + VII = XIIIIVII = XVIIIIII


We already have a concatenation operator, but we would need to write a custom sorting function for Roman numerals. If we're going to go that much trouble, it might be better to just build a new string by counting the number of each numeral in the two strings we are given. This would allow us to handle the situation above where we now have six I's. Our next step is to combine similar numerals that are equal to the next larger numeral.


XVIIIIII = XVVI = XXI


In code, it would be best to start with the smallest numeral, I, and work our way towards the largest numeral.


Subtraction of Roman numerals involves expanding and cancelling until there is only one number left.


XIV - VI = XIIII - VI = XIII - V = VVIII - V = VIII


I leave it up to you to research how to perform multiplication and division of Roman numerals, and whether or not you want to write functions for Roman arithmetic. Such functions would not be useful in production code, but writing them would be a good exercise, and it would be interesting to compare the runtimes with conversion back to Arabic followed by a conversion to Roman again.


Next weekend is Easter, so we'll be writing code to calculate when Easter and its related holidays (Good Friday, Pentecost, etc.) occur for any given year.

Saturday, March 8, 2008

Roman Numerals, Part 1

This weekend we're going to write a function to convert an integer into a Roman numeral, because storing Roman numerals in a database as strings is not cool.


ASP

  1. function roman(ByVal arabic)
  2.     Dim ones
  3.     Dim tens
  4.     Dim hundreds
  5.     Dim thousands
  6.     ones = Array("", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX")
  7.     tens = Array("", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC")
  8.     hundreds = Array("", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM")
  9.     thousands = Array("", "M", "MM", "MMM", "MMMM")
  10.     if arabic > 4999 then
  11.         ' For large numbers (five thousand and above), a bar is placed above a base numeral to indicate multiplication by 1000.
  12.         ' Since it is not possible to illustrate this in plain ASCII, this function will refuse to convert numbers above 4999.
  13.         Err.Clear
  14.         Err.Raise 9
  15.     elseif arabic = 0 then
  16.         ' About 725, Bede or one of his colleagues used the letter N, the initial of nullae,
  17.         ' in a table of epacts, all written in Roman numerals, to indicate zero.
  18.         roman = "N"

  19.     else
  20.         roman = thousands((arabic - (arabic mod 1000)) / 1000)
  21.         arabic = arabic mod 1000
  22.         roman = roman & hundreds((arabic - (arabic mod 100)) / 100)
  23.         arabic = arabic mod 100
  24.         roman = roman & tens((arabic - (arabic mod 10)) / 10)
  25.         arabic = arabic mod 10
  26.         roman = roman & ones((arabic - (arabic mod 1)) / 1)
  27.         arabic = arabic mod 1
  28.     end if
  29. end function

PHP

  1. function roman($arabic)
  2. {
  3.     $ones = Array("", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX");
  4.     $tens = Array("", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC");
  5.     $hundreds = Array("", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM");
  6.     $thousands = Array("", "M", "MM", "MMM", "MMMM");
  7.     if ($arabic > 4999)
  8.     {
  9.         // For large numbers (five thousand and above), a bar is placed above a base numeral to indicate multiplication by 1000.
  10.         // Since it is not possible to illustrate this in plain ASCII, this function will refuse to convert numbers above 4999.
  11.         die("Cannot represent numbers larger than 4999 in plain ASCII.");
  12.     }
  13.     elseif ($arabic == 0)
  14.     {
  15.         // About 725, Bede or one of his colleagues used the letter N, the initial of nullae,
  16.         // in a table of epacts, all written in Roman numerals, to indicate zero.
  17.         return "N";
  18.     }
  19.     else
  20.     {
  21.         $roman = $thousands[($arabic - fmod($arabic, 1000)) / 1000];
  22.         $arabic = fmod($arabic, 1000);
  23.         $roman .= $hundreds[($arabic - fmod($arabic, 100)) / 100];
  24.         $arabic = fmod($arabic, 100);
  25.         $roman .= $tens[($arabic - fmod($arabic, 10)) / 10];
  26.         $arabic = fmod($arabic, 10);
  27.         $roman .= $ones[($arabic - fmod($arabic, 1)) / 1];
  28.         $arabic = fmod($arabic, 1);
  29.         return $roman;
  30.     }
  31. }

You may have noticed in the PHP example that I used the fmod() function instead of the % operator. That was a clue that you might want to add support for floating point integers. You can learn how to represent fractions from the Wikipedia article on Roman numerals. Here's a couple of hints to get you started:


  • You will need another array to hold the numerals for the fractional values.
  • The fractional portion should be handled after the whole number portion has been converted and modularly divided out.
  • However, pre-check the rounding of the fraction before you start the conversion. If it rounds to a whole number, round it off before the conversion to prevent problems.

Next weekend, we're going to write some utility functions that will be useful for turning Roman numerals back into integers, or if you're feeling particularly crazy, performing mathematical calculations on Roman numerals. The weekend after that is Easter weekend, so we'll write a function to calculate Easter and the related holidays surrounding it.