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

Reply via email to