Sunday, April 18, 2010

Roman numbers converter in C#

Following methods written in C#, can be used for converting of numbers from Roman to decimal numerals (int) and vice versa. Only numbers in simple roman notation can be used. Simple Roman notations only includes ‘I’, ‘V’, ‘X’, ‘L’, ‘C’, ‘D’, ‘M’ digits and can represent numbers from 1 to 3999.
public class RomanNumbers
    {
        public static string ConvertFromInteger(int integerNumber)
        {
            if (integerNumber < 1)
                return String.Empty;
            if (integerNumber > 3999)
                throw new ArgumentException("integerNumber. Should be less than 3999");

            const string romanSymbols = "IVXLCDM";
            var orderOfNumber = (int)Math.Truncate(Math.Log10(integerNumber));

            var currentNumber = integerNumber;
            var sbRomanNumber = new StringBuilder();

            for (var i = orderOfNumber; i >= 0; i--)
            {
                int j = i * 2;
                int devisor = GetCurrentDevisor(i);
                int valueOfCurrentPower = (int)Math.Truncate((double)(currentNumber / devisor));

                switch (valueOfCurrentPower)
                {
                    case 1:
                        sbRomanNumber.Append(romanSymbols[j]);
                        break;
                    case 2:
                        sbRomanNumber.Append(romanSymbols[j], 2);
                        break;
                    case 3:
                        sbRomanNumber.Append(romanSymbols[j], 3);
                        break;
                    case 4:
                        sbRomanNumber.Append(romanSymbols[j]);
                        sbRomanNumber.Append(romanSymbols[j + 1]);
                        break;
                    case 5:
                        sbRomanNumber.Append(romanSymbols[j + 1]);
                        break;
                    case 6:
                        sbRomanNumber.Append(romanSymbols[j + 1]);
                        sbRomanNumber.Append(romanSymbols[j]);
                        break;
                    case 7:
                        sbRomanNumber.Append(romanSymbols[j + 1]);
                        sbRomanNumber.Append(romanSymbols[j], 2);
                        break;
                    case 8:
                        sbRomanNumber.Append(romanSymbols[j + 1]);
                        sbRomanNumber.Append(romanSymbols[j], 3);
                        break;
                    case 9:
                        sbRomanNumber.Append(romanSymbols[j]);
                        sbRomanNumber.Append(romanSymbols[j + 2]);
                        break;
                }
                currentNumber -= valueOfCurrentPower * devisor;
            }
            return sbRomanNumber.ToString();
        }

        public static int ConvertToInteger(string romanNumber)
        {
            if (romanNumber == null)
                throw new ArgumentNullException("romanNumber");

            int resultInteger = 0;

            romanNumber = romanNumber.ToUpperInvariant();

            for (int i = 0; i < romanNumber.Length; i++)
            {
                switch (romanNumber[i])
                {
                    case 'I':
                        if (i < romanNumber.Length - 1 
                            && romanNumber[i + 1] != 'I')
                            resultInteger--;
                        else resultInteger++;
                        break;
                    case 'V':
                        resultInteger += 5;
                        break;
                    case 'X':
                        if (i < romanNumber.Length - 1 
                            && (romanNumber[i + 1] == 'L' 
                            || romanNumber[i + 1] == 'C'))
                            resultInteger -= 10;
                        else resultInteger += 10;
                        break;
                    case 'L':
                        resultInteger += 50;
                        break;
                    case 'C':
                        if (i < romanNumber.Length - 1 
                            && (romanNumber[i + 1] == 'D' 
                            || romanNumber[i + 1] == 'M'))
                            resultInteger -= 100;
                        else
                            resultInteger += 100;
                        break;
                    case 'D':
                        resultInteger += 500;
                        break;
                    case 'M':
                        resultInteger += 1000;
                        break;
                    default:
                        throw new ArgumentException("Wrong Roman number format.");
                }
            }
            return resultInteger;
        }

        private static int GetCurrentDevisor(int i)
        {
            return (int)Math.Pow(10, i);
        }
    }
Here are the unit tests:
[TestFixture]
    public class RomanNumbersTests
    {
        private object[] _convertTestCases = {
                                                 new object[] {"I", 1},
                                                 new object[] {"II", 2},
                                                 new object[] {"III", 3},
                                                 new object[] {"IV", 4},
                                                 new object[] {"V", 5},
                                                 new object[] {"VI", 6},
                                                 new object[] {"VII", 7},
                                                 new object[] {"VIII", 8},
                                                 new object[] {"IX", 9},
                                                 new object[] {"X", 10},
                                                 new object[] {"XI", 11},
                                                 new object[] {"XII", 12},
                                                 new object[] {"XIII", 13},
                                                 new object[] {"XIV", 14},
                                                 new object[] {"XV", 15},
                                                 new object[] {"XVI", 16},
                                                 new object[] {"XVII", 17},
                                                 new object[] {"XVIII", 18},
                                                 new object[] {"XIX", 19},
                                                 new object[] {"XX", 20},
                                                 new object[] {"XXI", 21},
                                                 new object[] {"XXV", 25},
                                                 new object[] {"XXX", 30},
                                                 new object[] {"XXXV", 35},
                                                 new object[] {"XL", 40},
                                                 new object[] {"XLV", 45},
                                                 new object[] {"XLIX", 49},
                                                 new object[] {"L", 50},
                                                 new object[] {"LX", 60},
                                                 new object[] {"LXIX", 69},
                                                 new object[] {"LXX", 70},
                                                 new object[] {"LXXVI", 76},
                                                 new object[] {"LXXX", 80},
                                                 new object[] {"XC", 90},
                                                 new object[] {"XCIX", 99},
                                                 new object[] {"C", 100},
                                                 new object[] {"CL", 150},
                                                 new object[] {"CC", 200},
                                                 new object[] {"CCC", 300},
                                                 new object[] {"CD", 400},
                                                 new object[] {"CDXCIX", 499},
                                                 new object[] {"D", 500},
                                                 new object[] {"DC", 600},
                                                 new object[] {"DCLXVI", 666},
                                                 new object[] {"DCC", 700},
                                                 new object[] {"DCCC", 800},
                                                 new object[] {"CM", 900},
                                                 new object[] {"CMXCIX", 999},
                                                 new object[] {"M", 1000},
                                                 new object[] {"MCDXLIV", 1444},
                                                 new object[] {"MDCLXVI", 1666},
                                                 new object[] {"MCMXC", 1990},
                                                 new object[] {"MCMXCIX", 1999},
                                                 new object[] {"MM", 2000},
                                                 new object[] {"MMI", 2001},
                                                 new object[] {"MMX", 2010},
                                                 new object[] {"MMD", 2500},
                                                 new object[] {"MMM", 3000},
                                                 new object[] {"MMMDCCCLXXXVIII", 3888},
                                                 new object[] {"MMMCMXCIX", 3999}
                                             };

        [Test]
        public void ConvertFromInteger_Returns_EmptyString_For_Zero()
        {
            Assert.IsEmpty(RomanNumbers.ConvertFromInteger(0));
        }

        [Test]
        public void ConvertFromInteger_Returns_EmptyString_For_NegativeNumber()
        {
            Assert.IsEmpty(RomanNumbers.ConvertFromInteger(-1));
            Assert.IsEmpty(RomanNumbers.ConvertFromInteger(-999));
            Assert.IsEmpty(RomanNumbers.ConvertFromInteger(Int32.MinValue));
        }

        [Test]
        [ExpectedException(typeof(ArgumentException))]
        public void ConvertFromInteger_ThrowsArgumentException_For_Number_Greater_Tnan_3999()
        {
            RomanNumbers.ConvertFromInteger(4000);
        }

        [Test]
        public void ConvertFromInteger_WorksFine_For_Numbers_Between_1_And_3999()
        {
            for (var i = 1; i < 4000; i++)
                RomanNumbers.ConvertFromInteger(i);
        }

        [Test]
        [TestCaseSource("_convertTestCases")]
        public void ConvertFromInteger_Works(string romanNumber, int numberToConvert)
        {
            Assert.AreEqual(romanNumber, RomanNumbers.ConvertFromInteger(numberToConvert));
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ConvertToInteger_RisesArgumnetNullException_If_InputStringIsNull()
        {
            RomanNumbers.ConvertToInteger(null);
        }

        [Test]
        [ExpectedException(typeof(ArgumentException))]
        public void ConvertToInteger_RisesArgumnetException_If_InputStringIsNotRomanNumber()
        {
            RomanNumbers.ConvertToInteger("sddfjslf9321233jrlew49jkrlwr3");
        }
        
        [Test]
        [TestCaseSource("_convertTestCases")]
        public void ConvertToInteger_Works(string romanNumberToConvert, int resultNumber)
        {
            Assert.AreEqual(resultNumber, RomanNumbers.ConvertToInteger(romanNumberToConvert));
        }
    }
Unit tests' results:

No comments: