shaun mccormick

programming and software architecture

Contact Me

<- home

Translating Roman Numerals

()

Earlier today I was packing up a great book on the decline of the Roman Empire, called “The Inheritance of Rome”, of which I highly recommend. The author goes into how Rome never really declined, but slowly atrophied, due to a combination of decentralizing social structures, the collapse of the Roman transporation system and maintenance of roads, and how the inability of Rome to collect proper taxation later on was one of the main catalysts to the fall. Fascinating book.

But that’s not the point of this article. The book got me thinking; is there a way to convert Roman numerals into standard Western numerals? Specifically, could I do this in PHP? Well, why not?

Alphabets and Numbers

Most all of us know the representation for Roman numerals today. I put these into a simple PHP associative array for easy mapping:

$map = array(
    'I' => 1,
    'V' => 5,
    'X' => 10,
    'L' => 50,
    'C' => 100,
    'D' => 500,
    'M' => 1000,
);

Seems simple enough, right? Just a straightforward string-replacement iteration and sum, right? Here’s our method:

function etTuBrute($str,array $map) {
    $len = strlen($str);
    $total = 0;
    for ($i=0;$i<$len;$i++) {
        $current = $str[$i];
        $total += $map[$current];
    }
    return $total;
}

So, this makes the following values:

echo etTuBrute('XVI',$map); // 16
echo etTuBrute('MXI',$map); // 1011
echo etTuBrute('LVI',$map); // 56
// ...
echo etTuBrute('XIX',$map); // 21 (wrong!)
echo etTuBrute('MCDLXXIX',$map); // 1681 (wrong!)

Dang. Nope. Those Romans, they were tricksy folk. As most of you probably remember, the way Roman numerals work is that you start off with the highest-value character first, and then proceed rightward in descending value. So, XVI is “10-5-1”, which added together makes 16.

However, there’s the little catch they did, which is that if you run into a higher value number after a lower one, that means that the lower value number is actually a combination number. In other words, XIX is 19, since X = 10, and IX = 9.

So how is this done logically? Well, we still can loop through the characters in the string, but we’ll need to do some “pre-checking” to make sure the current character is of the same or higher value than the lesser character. So here’s our function now:

function etTuBrute($str,array $map) {
    $len = strlen($str);
    $total = 0;
    for ($i=0;$i<$len;$i++) {
        $current = $str[$i];
        if ($i === $len - 1) {
            $total += $map[$current];
        } else {
            $total += $map[$current] < $map[$str[$i+1]] ? -$map[$current] : $map[$current];
        }
    }
    return $total;
}

And our results from earlier:

echo etTuBrute('XVI',$map); // 16
echo etTuBrute('MXI',$map); // 1011
echo etTuBrute('LVI',$map); // 56
// ...
echo etTuBrute('XIX',$map); // 19
echo etTuBrute('MCDLXXIX',$map); // 1479

Great! It works now. So in the loop, we first see if the character position is at the end. If so, we know that it’s value will always be what it says it is, so we just add it to the sum. If it’s not the end, we then go check to see what the value of the next character is. If that value is higher, we subtract the current value from the sum, since that’s really what Roman numerals are doing by prepending the lower-valued character.

This gets us the right values, and a great little translation function! Carpe diem!

comments powered by Disqus