/* * Program Name: romanize * Source: roman.c * Author: Barry Lake * Date: 29 February 2000 * Version: 1.3 * * Copyright: Copyright 2000 by Barry Lake * This source code is freely offered to the public without * warranty of any kind, implicit or explicit. Any subsequent * usage, publication or transmission of this code or any * derivation of it must include this copyright notice and all * other commentary. The use of this source code or any * derivation in a commercial software product or in any other * endeavor designed to generate profit is strictly * prohibited. * * Description: This program takes an Arabic number as input and * returns the equivalent Roman numeral string. * * Limitations: The limitations of this program are the same as the * limitations of the Roman numeral system: No fractions (ie, * no decimal points) and no negatives. In other words, only * positive integers are allowed. In addition, for this * implementation the maximum integer supported is 4,999,999. * * Conventions: There are many quirks in the construction of Roman * numerals. The Romans themselves were inconsistent in their * usage, particularly in their application of the * "subtraction rule". A fine example of this is in the * Coliseum where they labeled entrance #49 as XLVIIII. Notice * that the high order (tens) portion of the number employs * the subtraction rule: XL or 50 - 10, but the low order * (ones) portion does not: VIIII or 5 + 4. * * By contrast, this program always uses the most compact * format available (with one exception, see below), so 49 * would be rendered as XLIX. * * Numbers larger than 3999 (MMMCMXCIX) become problematic. * Since there is no character that represents 5000, we can't * use the subtraction rule to create a compact representation * of 4000 and are forced to use MMMM. Continuing in that * vein, 5000 would be MMMMM and 6000 would be MMMMMM, etc. * This is obviously intolerable, although the Romans actually * did it occasionally. Luckily, they usually used some sort * of "multiplicative" convention to convert their lower order * digits into higher order ones. The most common method was * to place a horizontal bar over a digit so as to multiply it * by 1000. For example, a V with bar is 5000 and an M with * bar is 1000000. * * Now, in the interests of keeping this program simple (and * ANSI compliant), I can't use special characters, character * enhancements (bold, italic, etc), or display enhancemnets * (halftone, inverse, etc) to denote the higher order values. * Instead the case of the letters is used, lower case for the * lower order numbers and upper case for the higher orders. * In this way, v=5 and V=5000, c=100 and C=100000, and so on. * * Usage: In this version the program expects to be passed a command * line argument which is a valid Arabic integer in the range * 1..4999999. It displays the Roman equivalent and quits. * Therefore the program must be run once for each conversion. * A future version may prompt the user for input and perform * conversions until told to quit. Another option might be to * accept any number of command line arguments and treat each * one as a number to convert. * * Comments: In certain places where particularly long comments might * clutter up the code too much, a "footnote" flag in the form * "" appears. The full comments corresponding to the flag * can be found after the end of the function in the footnotes * section for the function. */ /* macros */ #define AUTHOR "Barry Lake" #define PROGNAME "ROMAN" #define VERSION "v1.4" #define YEAR "2000" #define and && #define or || #define not ! #define PRINTF (void) printf /* these macros help keep the code */ #define FPRINTF (void) fprintf /* a little cleaner and easy to read */ /* #define MacOS remember to comment this out if you're not on MacOS! */ /* C library includes */ #include #include #include #include /* other includes */ #ifdef MacOS #include /* provides functionality to bring up a console window * at runtime that allows the program to be given * command line arguments, which normally wouldn't be * possible in the MacOS environment */ #endif /* prototypes */ void convert_digit ( char arabic_digit, int order, char *roman ); void show_usage(void); char *strltrim(char *s); char *strrtrim(char *s); char *zerotrim(char *s); /* * main * */ int main(int argc, char *argv[]) { int len, i; char arabic_digit, *arabic_string, roman_digit[5], roman_string[30]; #ifdef MacOS argc = ccommand(&argv); /* bring up a pseudo command line interface */ #endif if (argc < 2) { show_usage(); return ( EXIT_SUCCESS ); } else if (argc > 2) { FPRINTF(stderr, "All arguments after the first are ignored.\n"); } /* <1> */ arabic_string = (char *) malloc (strlen(argv[1]) * sizeof(char) + 1); if ( not arabic_string ) { FPRINTF(stderr, "ERROR: malloc failed to accommodate your input.\n"); return ( EXIT_FAILURE ); } (void) strcpy(arabic_string, argv[1]); /* <2> */ arabic_string = strltrim(strrtrim(arabic_string)); /* <3> */ arabic_string = zerotrim(arabic_string); /* <4> */ len = strlen(arabic_string); if ( len == 0 ) { FPRINTF(stderr, "Sorry, 0 can't be represented as a Roman numeral!\n"); return ( EXIT_FAILURE ); } for(i=0; i 7 ) { FPRINTF(stderr, "%s\n", "This program only handles 7-digit numbers or smaller."); return ( EXIT_FAILURE ); } else if ( len == 7 and arabic_string[0] >= '5') { FPRINTF(stderr, "Sorry, the maximum number allowed is 4999999.\n"); return ( EXIT_FAILURE ); } roman_string[0] = '\0'; for (i=0; i */ (void) strcat(roman_string, roman_digit); } free( (void *) arabic_string ); PRINTF("%s\n", roman_string); return ( EXIT_SUCCESS ); } /* main */ /* * main footnotes * * <1> I generally try to avoid malloc, but in this case I don't know in * advance how large a string the user will input. * * <2> Frequently, I need the contents of one or more of the command line * arguments in some function other than main. I usually accomplish this * by copying the argument to a global. In this program I don't happen to * need the argument outside of main, but I still copy it to another * variable for consistency's sake. In any case, I generally try to avoid * operating directly on any of the command line arguments. * * <3> Trimming off the white space is probably unnecessary here, as the * command line arguments should already be trimmed. However, a future * implementation may interactively accept user input for processing, * and in that case, the arguments definitely should be trimmed. * * <4> Trimming leading zeros also shouldn't be necessary, but there's always * a wise guy out there who will try to break the program by entering in * nothing but a string of zeros just to see what happens. * * <5> The "len-i-1" expression calculates the index into the particular row * of the Arabic to Roman conversion table that matches the order of the * digit being converted. The convert_digit function is called once for * each digit of the Arabic integer, starting with the most significant * position and iterating to the right to the least significant digit. * The result after each iteration is appended to the final Roman numeral * string which is constructed from left to right. */ /* * convert_digit * * This function is the real "meat" of the program. It takes a single numeric * character (Arabic digit) and creates the equivalent Roman numeral string * based upon the "order" (ones, tens, hundreds, etc.) of the Arabic digit. * The right-most digit in the Arabic number, occupying the ones place, is * considered to be order 0. The next digit to the left, occupying the tens * place is order 1, and so on up to the millions place, which is order 6. * */ void convert_digit ( char arabic_digit, int order, char *roman_digit ) { char roman_table[][3] = /* <6> */ { {'i','v','x'}, {'x','l','c'}, {'c','d','m'}, {'m','V','X'}, {'X','L','C'}, {'C','D','M'}, {'M'} }; if ( order == 3 and arabic_digit >= '6' ) { roman_table[order][0] = 'I'; /* <7> */ } switch (arabic_digit) { case '0' : roman_digit[0] = '\0'; break; case '1' : roman_digit[0] = roman_table[order][0]; roman_digit[1] = '\0'; break; case '2' : roman_digit[0] = roman_table[order][0]; roman_digit[1] = roman_table[order][0]; roman_digit[2] = '\0'; break; case '3' : roman_digit[0] = roman_table[order][0]; roman_digit[1] = roman_table[order][0]; roman_digit[2] = roman_table[order][0]; roman_digit[3] = '\0'; break; case '4' : if ( order == 3 or order == 6 ) /* <8> */ { roman_digit[0] = roman_table[order][0]; roman_digit[1] = roman_table[order][0]; roman_digit[2] = roman_table[order][0]; roman_digit[3] = roman_table[order][0]; roman_digit[4] = '\0'; } else { roman_digit[0] = roman_table[order][0]; roman_digit[1] = roman_table[order][1]; roman_digit[2] = '\0'; } break; case '5' : roman_digit[0] = roman_table[order][1]; roman_digit[1] = '\0'; break; case '6' : roman_digit[0] = roman_table[order][1]; roman_digit[1] = roman_table[order][0]; roman_digit[2] = '\0'; break; case '7' : roman_digit[0] = roman_table[order][1]; roman_digit[1] = roman_table[order][0]; roman_digit[2] = roman_table[order][0]; roman_digit[3] = '\0'; break; case '8' : roman_digit[0] = roman_table[order][1]; roman_digit[1] = roman_table[order][0]; roman_digit[2] = roman_table[order][0]; roman_digit[3] = roman_table[order][0]; roman_digit[4] = '\0'; break; case '9' : roman_digit[0] = roman_table[order][0]; roman_digit[1] = roman_table[order][2]; roman_digit[2] = '\0'; break; /* the default case should never happen, * but we handle it, just to be thorough... */ default : roman_digit[0] = '\0'; FPRINTF(stderr, "%s %s\n", "WARNING: An invalid digit was passed to", "'convert_digit'. It will be ignored."); break; } /* switch */ } /* convert_digit */ /* * convert_digit footnotes * * <6> For any given position (order) in an Arabic number, the digit in that * place can be represented as a Roman numeral by using characters from * the row in the roman_table that corresponds to the order of the digit. * For example, all of the digits in the ones place (order 0, numbers 1 * through 9) can be made using characters from row 0 of the table. * In the same way, all of the digits in the tens place (order 1, numbers * (1 through 9)*10) can be made using the characters in row 1. The nice * thing is that at every position, all the way up to the hundred * thousands place, the numerals are constructed in exactly the same way: * 1 2 3 4 5 6 7 8 9 => i ii iii iv v vi vii viii ix * 10 20 30 40 50 60 70 80 90 => x xx xxx xl l lx lxx lxxx xc * and so on. * * <7> The conversion we do here is purely for aesthetic reasons. It is clear * that since this program supports fairly large numbers, many of the * Roman numerals will be mixed case, such as "LXVdxxxvi" for 65536. * What I don't want, however, is mixed case within a *single* place or * order. For example, if we use the roman_table as it is intialized, * then 6000 would yield "Vm" and even worse, 9000 would be "mX". Thus, a * number such as 19111 would give us "XmXcxi". Yuck! To get around this * unpleasantness for order 3, digits 6 through 9, we change "m" to "I" * in the roman_table. That way we get "XIXcxi" instead. Much nicer, eh? * Now, one might ask why I don't just initialize the roman_table with * "I" instead of "m" to begin with. Well, in that case we would get * "Icmxcix" for 1999 instead of the correct "mcmxcix". * * <8> In most cases, a 4 is created using the subtraction rule: 4=iv, 40=xl, * 400=cd. In the case of 4000 (order 3) it would certainly be possible * to use the subtraction rule and to represent it as "mV", but this * clearly is not desirable, as was discussed in note 7. Also, the Romans * themselves wrote 4000 as "mmmm", so I'm happy to honor that tradition. * As for 4000000 (order 6), it's not even possible to use the * subtraction rule to represent it, since there is no symbol for 5000000 * in the current scheme, so we must use "MMMM". Mm Mm good! This is why * 4999999 is the largest number supported by this program. */ /* * show_usage * */ void show_usage(void) { PRINTF("\n%s %s by %s, Copyright %s\n\n", PROGNAME, VERSION, AUTHOR, YEAR); PRINTF("This program expects to be passed an Arabic number (a positive\n" "integer) in the range 1..4999999. It returns the Roman numeral\n" "equivalent.\n\n"); PRINTF("Traditionally, a horizontal bar has been placed over a digit\n" "or series of digits to denote that the values are to be\n" "multiplied by 1000. This program instead uses the convention\n" "that lower case letters are used for 'face values' and upper\n" "case letters are to be multiplied by 1000.\n\n"); PRINTF("For example, following this convention v=5 whereas V=5000.\n" "Similarly, xl=40 and XL=40000. Using this system we would\n" "represent 65536 as LXVdxxxvi, and 3000003 as MMMiii.\n\n"); } /* show_usage */ /* * strltrim * * This function (borrowed from Pascal) takes any arbitrary string * and strips out all leading white space * * BBL, 1999-09-27 * */ char *strltrim(char *s) { char *ptr; ptr = s; while ( isspace((int)*ptr)) ptr++; if (ptr != s) (void) strcpy(s, ptr); return(s); } /* strltrim */ /* * strrtrim * * This function (borrowed from Pascal) takes any arbitrary string * and strips out all trailing white space * * BBL, 1999-09-27 * */ char *strrtrim(char *s) { char *ptr; ptr = s + strlen(s); while (ptr > s) { ptr--; if ( isspace((int)*ptr) ) *ptr = '\0'; else return (s); } return(s); } /* strrtrim */ /* * zerotrim * * This function (cloned from strltrim) strips the leading zeros (if any) * from a numeric string. This functionality could have been rolled into * strltrim, however that might have impaired its potential usefulness * elsewhere in the program. * */ char *zerotrim(char *s) { char *ptr; ptr = s; while ( ptr[0] == '0' ) ptr++; if (ptr != s) (void) strcpy(s, ptr); return(s); } /* zerotrim */