<?php
/**
* Class for converting decimal to Roman numbers and
* vice-versa.
*
* @author Nikola Posa, www.nikolaposa.in.rs
* @license GNU General Public License (GPL)
*/
class Dec2RomanNumConverter
{
/**
* Common Roman numerals array, with appropriate decimal
* numbers as indexes.
*
* @var array
*/
protected $basicNumbers = array
(
1 => 'I',
2 => 'II',
3 => 'III',
4 => 'IV',
5 => 'V',
6 => 'VI',
7 => 'VII',
8 => 'VIII',
9 => 'IX',
10 => 'X',
50 => 'L',
100 => 'C',
500 => 'D',
1000 => 'M',
5000 => '<SPAN STYLE = "BORDER-TOP: 1PX SOLID;">V</SPAN>',
10000 => '<SPAN STYLE = "BORDER-TOP: 1PX SOLID;">X</SPAN>'
);
/**
* Function for converting decimal numbers to Roman.
*
* @param int Decimal number.
* @return string.
*/
public function dec2roman($dn)
{
if ($dn > 10999) { //Cheks if argument is larger than 10999 because our algorithm can't process such large numbers.
throw new Exception('Are you kidding me?');
}
if (array_key_exists($dn, $this->basicNumbers)) { //If the argument is in common numbers array, there's no need to run whole algorithm.
return $this->basicNumbers[$dn];
}
//Here we go...
$romanNum = '';
$decNum = (string)$dn;
$decNumLength = strlen($decNum); //Getting number of numerals.
for ($i = 0; $i < $decNumLength; $i++) { //Looping through all numerals.
if ($i == $decNumLength - 1) { //If we've reached the last number, we just need to replace that number with proper number from the $basicNumbers array.
$romanNum .= $this->basicNumbers[$decNum{$i}];
}
else {
//Getting the base of the position of the current numeral (1, 10, 100, 1000...).
$base = pow(10, ($decNumLength - 1 - $i));
if (array_key_exists($decNum{$i} * $base, $this->basicNumbers)) { //If the numeral is some of the basic numbers in $basicNumbers array, we just need to get that number and stop this second loop.
$romanNum .= $this->basicNumbers[$decNum{$i} * $base];
}
elseif ((int)$decNum > 3999 && $i == 0) { //If the argument number is larger than 3999 and first numeral is being processed. For Roman numbers larger than 3999, a bar is often placed above number.
$romanNum .= '<SPAN STYLE = "BORDER-TOP: 1PX SOLID;">' . $this->basicNumbers[$decNum{$i}] . '</SPAN>';
}
elseif (in_array($decNum{$i}, array(4, 6, 7, 8, 9))) { //If current numeral is one of those "special" numerals, which must be writen by, so called, "subtractive principle". (IIII - wrong, IV - correct; VIIII - wrong, IX - correct, and so on...)
switch($decNum{$i}) {
case '4': //Prefix format for number 4.
{
$romanNum .= $this->basicNumbers[$base] . $this->basicNumbers[5 * $base];
break;
}
case '6': case '7': case '8': //Sufix format.
{
$romanNum .= $this->basicNumbers[5 * $base];
for ($k = 0; $k < ((int)$decNum{$i} - 5); $k++) {
$romanNum .= $this->basicNumbers[$base];
}
break;
}
case '9': //Prefix format for number 9.
{
$romanNum .= $this->basicNumbers[$base] . $this->basicNumbers[10 * $base];
break;
}
}
}
else { //Looping until we reach the value of current numeral, beause Roman numbers can be in format "XXVII", so we need to loop 2 times for that "2" in "27", to get "XX".
for ($j = 0; $j < (int)$decNum{$i}; $j++) {
$romanNum .= $this->basicNumbers[$base];
}
}
}
}
return $romanNum;
}
/**
* Function for converting Roman to decimal numbers.
*
* @param string Roman numeral.
* @return int.
*/
public function roman2dec($rn)
{
$romanNum = (string)$rn;
$romanNum = trim(strtoupper($romanNum));
//Here we go...
$decNum = 0;
if (in_array($romanNum, $this->basicNumbers)) { //If the argument is in common numbers array, there's no need to run whole algorithm.
return array_search($romanNum, $this->basicNumbers);
}
//Numbers with top bar, should be specially treated.
$pattern = '/<SPAN\sSTYLE\s=\s"BORDER-TOP\:\s1PX\sSOLID\;">([A-Z]+)<\/SPAN>/i';
if (preg_match($pattern, $romanNum, $matches)) {
$decNum += 1000 * array_search($matches[1], $this->basicNumbers);
$romanNum = preg_replace($pattern, '', $romanNum);
}
//Some special patterns that need to replaced before going into algorithm.
if (preg_match_all('/(IV|XL|CD|IX|XC|CM)/i', $romanNum, $matches)) {
$found = $matches[1];
if (!is_array($found)) {
$found = array($found);
}
$romanNum = str_replace($found, '', $romanNum);
foreach ($found as $rn) {
$parts = str_split($rn);
$value = array_search($parts[1], $this->basicNumbers) - array_search($parts[0], $this->basicNumbers);
$decNum += (int)$value;
}
}
$count = 1;
for ($i = 0; $i < strlen($romanNum); $i++) {
if ($romanNum{$i} == $romanNum{$i+1}) { //If numerals are repeating, we have to count that number of repeats.
$count++;
}
else { //Getting decimal number and multply it with $count.
$key = array_keys($this->basicNumbers, $romanNum{$i});
$decNum += $count * $key[0];
$count = 1;
}
}
return $decNum;
}
}
?>