Search code examples
croman-numerals

How to convert integer value to Roman numeral string?


How can I convert an integer to its String representation in Roman numerals in C ?


Solution

  • The easiest way is probably to set up three arrays for the complex cases and use a simple function like:

    // convertToRoman:
    //   In:  val: value to convert.
    //        res: buffer to hold result.
    //   Out: n/a
    //   Cav: caller responsible for buffer size.
    
    void convertToRoman (unsigned int val, char *res) {
        char *huns[] = {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"};
        char *tens[] = {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"};
        char *ones[] = {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};
        int   size[] = { 0,   1,    2,     3,    2,   1,    2,     3,      4,    2};
    
        //  Add 'M' until we drop below 1000.
    
        while (val >= 1000) {
            *res++ = 'M';
            val -= 1000;
        }
    
        // Add each of the correct elements, adjusting as we go.
    
        strcpy (res, huns[val/100]); res += size[val/100]; val = val % 100;
        strcpy (res, tens[val/10]);  res += size[val/10];  val = val % 10;
        strcpy (res, ones[val]);     res += size[val];
    
        // Finish string off.
    
        *res = '\0';
    }
    

    This will handle any unsigned integer although large numbers will have an awful lot of M characters at the front and the caller has to ensure their buffer is large enough.

    Once the number has been reduced below 1000, it's a simple 3-table lookup, one each for the hundreds, tens and units. For example, take the case where val is 314.

    val/100 will be 3 in that case so the huns array lookup will give CCC, then val = val % 100 gives you 14 for the tens lookup.

    Then val/10 will be 1 in that case so the tens array lookup will give X, then val = val % 10 gives you 4 for the ones lookup.

    Then val will be 4 in that case so the ones array lookup will give IV.

    That gives you CCCXIV for 314.


    A buffer-overflow-checking version is a simple step up from there:

    // convertToRoman:
    //   In:  val: value to convert.
    //        res: buffer to hold result.
    //   Out: returns 0 if not enough space, else 1.
    //   Cav: n/a
    
    int convertToRoman (unsigned int val, char *res, size_t sz) {
        char *huns[] = {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"};
        char *tens[] = {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"};
        char *ones[] = {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};
        int   size[] = { 0,   1,    2,     3,    2,   1,    2,     3,      4,    2};
    
        //  Add 'M' until we drop below 1000.
    
        while (val >= 1000) {
            if (sz-- < 1) return 0;
            *res++ = 'M';
            val -= 1000;
        }
    
        // Add each of the correct elements, adjusting as we go.
    
        if (sz < size[val/100]) return 0;
        sz -= size[val/100];
        strcpy (res, huns[val/100]);
        res += size[val/100];
        val = val % 100;
    
        if (sz < size[val/10]) return 0;
        sz -= size[val/10];
        strcpy (res, tens[val/10]);
        res += size[val/10];
        val = val % 10;
    
        if (sz < size[val) return 0;
        sz -= size[val];
        strcpy (res, ones[val]);
        res += size[val];
    
        // Finish string off.
    
        if (sz < 1) return 0;
        *res = '\0';
        return 1;
    }
    

    although, at that point, you could think of refactoring the processing of hundreds, tens and units into a separate function since they're so similar. I'll leave that as an extra exercise.