Location: PHPKode > scripts > Dec2RomanNumConverter > Dec2RomanNumConverter.php
<?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;
	}
}
?>
Return current item: Dec2RomanNumConverter