As the cycle of the season repeats every year so does the cycle of freshmen in CS that have to actually learn how to make the computer do something to pass their assigments. This year I met some CS bachelors at my uni and got interested in their introduction to programming course and assignments.

Being a young UNIX user I could only scoff at the decision of an acquaintance to actually use C++ for his assignments.

C++ serves experts very well (but isn’t kind to novices)

Bjarne Stroustrup

Many hours were spent on the assignment to convert numbers from one base to another and more lines of codes where added to make sure to convert from decimal to hexadecimal and back, from binary to decimal and back and so on…

But no one mentioned him the important principles of UNIX, how everything fit together, what tools he can use and how eventually data and code are represented so everything was hopeless and the assignment could not be completed in a simple way.

Instead a wonderful botch was written and eventually it compiled and worked but nothing new was learnt.

As I like to learn new thing and this is something of a challenge (bonus assignment so kind of more difficult) I spent some time on it and eventually this is my solution. It works on strings as they are the only kind of input that is stable across tools, it has some error checking on the inputs and can be extended to support bases larger than 16.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define INPUT_BASE 1
#define OUTPUT_BASE 2
#define INPUT_STRING 3

char alphabet[] = {
        '0',
        '1',
        '2',
        '3',
        '4',
        '5',
        '6',
        '7',
        '8',
        '9',
        'A',
        'B',
        'C',
        'D',
        'E',
        'F',
};

int string_len(char *s) {
        int l = 0;
        while (s[l] != '\0') {
                l++;
        }
        return l;
}

int parse_any(char c) {
        switch(c) {
        case '0':
                return 0;
        case '1':
                return 1;
        case '2':
                return 2;
        case '3':
                return 3;
        case '4':
                return 4;
        case '5':
                return 5;
        case '6':
                return 6;
        case '7':
                return 7;
        case '8':
                return 8;
        case '9':
                return 9;
        case 'A':
                return 10;
        case 'B':
                return 11;
        case 'C':
                return 12;
        case 'D':
                return 13;
        case 'E':
                return 14;
        case 'F':
                return 15;
        default:
                return -1;
        }
}

int parse_decimal(char *number_string) {
        int input_len = string_len(number_string);

        int accumulator = 0;
        int t = 1;
        for (int i = input_len-1, j = 0; i >= 0; i--, j++){
                int offset = number_string[i] - '0';
                if (offset >= 9) {
                        fprintf(stderr, "Invalid decimal at position %d in character %c\n", i, number_string[i]);
                }
                accumulator += offset * t;
                t *= 10;
        }
        return accumulator;
}


int main(int argc, char *argv[]) {
        if (argc != 4) {
                fprintf(stderr, "Usage: convert a b input\n");
                fprintf(stderr, "       a input base\n");
                fprintf(stderr, "       b output base\n");
                fprintf(stderr, "       input the input string\n");
                exit(EXIT_FAILURE);
        }
        int input_base  = parse_decimal(argv[INPUT_BASE]);
        if (input_base > 16) {
                fprintf(stderr, "Input bases greater than 16 are not supported\n");
                exit(EXIT_FAILURE);
        }

        int output_base = parse_decimal(argv[OUTPUT_BASE]);
        if (output_base > 16) {
                fprintf(stderr, "Output bases greater than 16 are not supported\n");
                exit(EXIT_FAILURE);
        }

        int input_len = (int) string_len(argv[INPUT_STRING]);
        
        int input_accumulator = 0;
        int t = 1; // input_base ^ 0
        for (int i = input_len-1, j = 0; i >= 0; i--, j++){
                int parsed_number = parse_any(argv[INPUT_STRING][i]);
                if (parsed_number != -1) {
                        if (parsed_number >= input_base) {
                                fprintf(stderr, "Character %c at position %i is out of range\n", argv[INPUT_STRING][i], i);
                                fprintf(stderr, "Using base %d\n", input_base);
                                fprintf(stderr, "Parsed character %d\n", parsed_number);
                                exit(EXIT_FAILURE);
                        }
                        input_accumulator += parsed_number * t;
                } else {
                        fprintf(stderr, "Could not convert character %c at position %i\n", argv[INPUT_STRING][i], i);
                        exit(EXIT_FAILURE);
                }
                t *= input_base;
        }

        t = 1; // output_base ^ 0
        int output_len = 0;
        // compute the number of digits in the
        // output base
        do {
                output_len++;
                t *= output_base;
        } while (input_accumulator >= t);

        char output[output_len + 1]; //  store the output while we process it right to left
        output[output_len] = '\0'; // terminate the string

        int i = output_len - 1;
        do {
                int remainder_offset = input_accumulator % output_base; // find the remainder
                input_accumulator -= remainder_offset; // subtract remainder, now input_accumulator is a multiple of the output base
                input_accumulator /= output_base; // update the accumulator
                output[i] = alphabet[remainder_offset];
                i--;
        } while (input_accumulator != 0);
        fprintf(stdout, "%s\n", output);

        exit(EXIT_SUCCESS);
}