Aaron Ingebrigtsen: > Sure, that would be great. :) I would understand the comments a lot better > than the code. :) Maybe I could figure out a QBasic port by reading your > comments. :)
Man, that was a lot of comment writing. Left as an exersize for the reader: make sure that the user doesn't try to pass in "2" as a binary number, etc. I hope I didn't fuck this program up too bad.... #include <stdlib.h> #include <string.h> #include <stdio.h> #include <ctype.h> /* Helper function for string_to_integer. You know how for hex * numbers, A-F (or a-f) correspond to 10-15? This thing returns 10 * for A, 11 for B, etc. * * In plain english: if c is a digit, compute and return its * numerical value, by subtracting the numerical value of the ASCII * character '0' from it. (ASCII is a silly little standard that * arbitrarily assigns a number to each character. '0' is number 48, * and '1' is 49.... get the picture? 0-9, a-z, and A-Z are all * ordered sequentially at some offset in the ASCII table. That's * what the "c - '0'" does - if this function is passed the * character '5' (ASCII value 53), we subtract '0' (ASCII 48), and * get (drum roll please!) 53 - 48 = 5. Same thing with letters. * * Otherwise, if c is not a digit.... well, it's similar, so I'll * let you figure out the toupper() trick, and the reason for adding * 10. If you're so inclined. */ static inline int char_to_integer(char c) { return isdigit(c) ? c - '0' : toupper(c) - 'A' + 10; } /* Here's our generic string to integer converter. * * We start looping with "p" pointing to the beginning of the * string, and we stop looping when we reach the end of the string. * "while (*++p)" means "keep looping while the character pointed to * by pointer p is not NULL - and increment p to point to the next * character _before_ checking." * * Yeah, the pointer incrementing stuff is confusing. I know. * * So, anyway, what are we doing in this loop? We've got this * "result" variable initialized to 0, and every iteration of the * loop, we assign it a new value, which is (as you can see) based * on its old value and the numerical value of the current character * in our string, pointed to by "p". * * An example: string_to_integer("17", 10). This should return the * number 17 (if you pass 16 for the last argument, it should return * 23, the value of "17" in hex). * * We start with p pointing to '1'. So the first loop, we're doing * * result = 0 * 10 + 1 * * and now result = 1. We increment p and check if it's NULL - it's * not NULL, it's actually '7', the next character. So we loop * again: * * result = 1 * 10 + 7 * * and now result = 17. Increment p, it's NULL (end of string), we * stop and return our result. */ static inline unsigned int string_to_integer(char *p, unsigned int base) { int result = 0; do result = result * base + char_to_integer(*p); while (*++p); return result; } /* Converts a number to its ASCII character representation. We do it * by filling an array with the correct ASCII character for the * number at each index. So digits[9] = '9', etc. */ static inline char integer_to_char(unsigned int n) { const char digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; return digits[n]; } /* Here's our generic integer to string converter. It's even more * fun than string_to_integer(), if such a thing is possible. It's * what Aaron was talking about when he mentioned remainders and * division and what-not. * * At the top we've got a couple variables - "s", a string of 32 * characters, and "p", a pointer to the last character of "s". * * We're going to fill in that string from left to right and return * a pointer (p) to where our number starts. * * "if (!n)" is a special case for when the integer we're passed is * 0 - and it gives me an opportunity to explain the latest horrible * incarnation of pointer arithmetic! * * "*--p = '0'" means "decrement the pointer p, and then set the * character it now points to to '0'". Fun, huh? * * And then we'd skip the stuff after "else", of course, and go * right to "return p". And what do you know, p points to '0', just * like we want. All thanks to wonderful pointer arithmetic! * * OK, and what if "n" isn't 0? Now we do the remainder shit. An * example: integer_to_string(17, 10). * * First iteration of the loop, "p" points to the end of the string. * We decrement it and assign 17 % 10 = 7, converted to ASCII * encoding. * * Now for "while ((n /= base) > 0)". That means "divide n by base, * assign it the result, and keep looping if that result is greater * than 0". So, n = 17 / 10 = 1 (integer division in C truncates 1.7 * to 1), and 1 is greater than 0, so we keep looping. * * Next iteration, "p" points to '7', and "n" is 1. We decrement "p" * and assign it 1 % 10 = 1. * * And 1 / 10 = 0, so we stop looping, and return p, which points to * '1'. And look, the next character is '7', and then a NULL string * terminator. (Remember how we decremented _before_ assigning? That * string was initialized to all NULL characters, so decrementing * gave us a NULL terminator.) */ static inline char *integer_to_string(unsigned int n, unsigned int base) { static char s[33]; char *p = s + sizeof(s) - 1; if (!n) *--p = '0'; else do *--p = integer_to_char(n % base); while ((n /= base) > 0); return p; } /* These are wrapper functions. They call string_to_integer or * integer_to_string. * * If you ever had a number format that couldn't be handled by those * two functions, you could deal with them here (for instance you * might create an input_roman_numeral and output_roman_numeral). */ static unsigned int input_hex(char *s) { return string_to_integer(s, 16); } static char *output_hex(unsigned int n) { return integer_to_string(n, 16); } static unsigned int input_decimal(char *s) { return string_to_integer(s, 10); } static char *output_decimal(unsigned int n) { return integer_to_string(n, 10); } static unsigned int input_octal(char *s) { return string_to_integer(s, 8); } static char *output_octal(unsigned int n) { return integer_to_string(n, 8); } static unsigned int input_binary(char *s) { return string_to_integer(s, 2); } static char *output_binary(unsigned int n) { return integer_to_string(n, 2); } /* Here's a table - an array of structures. Each structure contains * a string (the name of the format) and two functions, one for * converting a string of the specified format to an integer, and * one for integer to string conversions. * * I've added the above eight functions here and given each pair a * name. * * The formats[] table allows us to easily extend our program to * support new number formats. We could do the same thing other * ways, of course, but I think tables like this one are neat. */ static struct { char *name; unsigned int (*input)(char *s); char *(*output)(unsigned int n); } formats[] = { {"hex", input_hex, output_hex}, {"decimal", input_decimal, output_decimal}, {"octal", input_octal, output_octal}, {"binary", input_binary, output_binary}, {} }; int main(int argc, char **argv) { char *input_string = argv[1]; char *input_format = argv[2]; char *output_format = argv[3]; char *output_string; /* These are pointers to functions. "input" can be assigned * any function which returns and int and takes a "char *" * argument. Like the input functions in the table, hint * hint. * * But right now I'm assigning them to NULL, which is sort * of a magic value. */ unsigned int (*input)(char *) = NULL; char *(*output)(unsigned int) = NULL; int i; if (argc != 4) { fprintf(stderr, "Usage: %s <number> <input format> <output format>\n" "Supported number formats:", argv[0]); for (i = 0; formats[i].name; i++) printf(" %s", formats[i].name); putchar('\n'); return 1; } /* At the top of this function we've initialized * input_format and output_format - they're two of the * arguments passed to the program. This loop looks through * the formats[] table and tries to find the entry that * handles each. If it does, it sets our input or output * function pointer to the right function: if input_format * was "hex", it sets input to input_hex, since that's * what's specified for "hex" in the table. */ for (i = 0; formats[i].name; i++) { if (!strcmp(input_format, formats[i].name)) input = formats[i].input; if (!strcmp(output_format, formats[i].name)) output = formats[i].output; } /* Did we find functions for the formats specified? */ if (!input) { fprintf(stderr, "Unknown input format: %s\n", input_format); return 1; } else if (!output) { fprintf(stderr, "Unknown output format: %s\n", output_format); return 1; } /* And here's where we call those functions we found in that * loop. An example: say I run this program with the * arguments "17 decimal hex" - that is, convert 17 from * decimal to hex. It sets input = input_decimal, output = * output_decimal, in that loop. Then they are called right * here: * * output_string = output_hex(input_decimal("17")); * * is what the computer executes. */ output_string = output(input(input_string)); printf("Converted %s %s to %s %s.\n", input_format, input_string, output_format, output_string); return 0; } _______________________________________________ Chat mailing list Chat at freenetproject.org http://lists.freenetproject.org/mailman/listinfo/chat