Home > database >  How do I properly convert to any base?
How do I properly convert to any base?

Time:02-05

My code compiled successfully, but any time I run the argument ./base.o 42 2, which converts it to base 2 in binary, the output is 0, and anytime I run ./base.o 99 5, the result is 444 repeatings.

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

int main(int argc, char *argv[]) {
    int remainder = 0;
    int num0 = 0;
    int i = 0;

    int num1 = atoi(argv[1]);
    int num2 = atoi(argv[2]);
    int quotient = num1;

    while (quotient != 0) {
        remainder = num1 % num2;
        quotient = quotient / num2;

        num0 = (remainder * pow(10, i))   num0;
        i  ;
    }

    printf("%d\n", num0);

    return 0;
}

CodePudding user response:

There are multiple issues in the code:

  • the program arguments seem to be expressed in base 10 and the output must be produced in the base corresponding to the second argument.
  • for this, the conversions int num1 = atoi(argv[1]); and int num2 = atoi(argv[2]); are correct, assuming there are at least 2 arguments: you should test the actual number of arguments received by the program.
  • but the computation in the loop is incorrect because you use remainder = num1 % num2; instead of remainder = quotient % num2; so you always get the last digit replicated.
  • your approach is very limited as you can only handle bases up to 10 and numbers with at most 10 digits in the target base.
  • you should instead store the remainders in an array and output these remainders as digits in reverse order.

Here is a modified version:

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

int main(int argc, char *argv[]) {
    int remainder[32];
    int num1, num2, quotient, i;
    const char *digits = "0123456789abcdefghijklmnopqrstuvwxyz";

    if (argc > 2) {
        num1 = atoi(argv[1]);
        num2 = atoi(argv[2]);

        if (num2 < 2 || num2 > 36) {
            printf("invalid base: %d\n", num2);
            return 1;
        }
        quotient = num1;
        i = 0;
        /* using a for(;;) loop instead of `while (quotient != 0)` to
           produce at least 1 digit for quotient == 0
        */
        for (;;) {
            /* using abs to handle negative numbers all the way to INT_MIN */
            remainder[i  ] = abs(quotient % num2);
            quotient = quotient / num2;
            if (quotient == 0)
                break;
        }

        if (num1 < 0) {
            putchar('-');
        }
        while (i > 0) {
            putchar(digits[remainder[--i]]);
        }
        putchar('\n');
    }
    return 0;
}

CodePudding user response:

Number base is just for humans to read. A number is a shorthand way of writing polynomial of increasing powers of the radix.

For example, the number 3057 with base (== radix) 10 is:

      3×103     0×102     5×101     7×100

I can totally change the radix:

      B×162     F×161     1×160

We see that B (11)×16×16 plus F (15)×16 plus 1 is 2816 240 1 = 3057 — the very same number as before.

Text to Integer

Converting from the text representation to a numeric value is easy:

int value = 0;
for (char * s = text_number;  *s;  s  )
  value = (value * radix)   value_of( *s );

That “value of c” thing is just to say that '7' should be transformed to a 7 and things like 'E' should be transformed to 15 (since the ASCII representation of a digit != that digit). This kind of thing is easiest done with a lookup table.

Integer to Text

Going the other way is almost as easy, you just need to print the digits in the right order. It helps to have a string to hold those digits.

char buffer[ N ] = { 0 };   // where N is big enough to hold all the digits
char * s = buffer   N - 1;  // start at the back
while (value)
{
  *(--s) = digit_for( value % radix );
  value /= radix;
}
puts( s );

Again, that “digit for” converts values like 7 to '7' and 15 to 'E'. Again a simple lookup table (the very same one as before, actually) is useful here.

If value is 0 we should just puts( "0" ) and avoid the loop.

Edge Cases

Alas, in computing, life is never as simple as we would like it to be. We need to deal with things like negative numbers and INT_MIN and zeros and stuff.

For what you are doing, I wouldn’t worry about it. But if it matters, you can keep a textual representation of INT_MIN (or whatever data type you are working against) to compare against the input, and assign that value directly if the input matches it.

You can even generate this minimum value representation by converting the largest possible value, adding one to the last character in the string, and slapping a minus sign on the front of it.

Of course, if you are working over an unsigned type, you don’t need to bother.

You still need to be aware of overflow, though. If you get more digits than your resulting type can hold...

And we see where the weeds makes this stuff get kind of tricky. The basic idea is simple, just make sure to handle all the weird edge cases where stuff goes wonky.

CodePudding user response:

This looked like a fun question to work on, and I see during the time I was refactoring the initial program that other good alternatives have been offered up. To that mix, I offer up this refactored version of the base conversion program.

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

#define MAX 100

char derive(int digit)
{
    char value;
    if (digit < 10)
    {
        value = (char)digit   '0';
    }
    else
    {
        switch(digit)
        {
            case 10:
                value = 'A';
                break;
            case 11:
                value = 'B';
                break;
            case 12:
                value = 'C';
                break;
            case 13:
                value = 'D';
                break;
            case 14:
                value = 'E';
                break;
            case 15:
                value = 'F';
                break;
            default:
                value = '*';
                break;
        }
    }
    return value;
}

int main(int argc, char *argv[]) {
    char cnv[MAX];
    int remainder = 0;
    int num0      = 0;
    int i         = 0;

    int num1 = atoi(argv[1]);
    int num2 = atoi(argv[2]);
    int quotient = num1;

    if (num2 < 2 || num2 > 16)
    {
        printf("Currently, only base numbers from 2 to 16 are allowed\n");
        return 0;
    }

    num0 = num2;

    for (int j = 0; j < MAX; j  )           /* Initialize the character array to terminator values */
    {
        cnv[j] = '\0';
    }

    while (1)                               /* Determine the largest power of the base needed for deriving a converted value */
    {
        num0 = num0 * num2;
        if (num0 > num1)
        {
            num0 = num0 / num2;
            break;
        }
    }

    while (num0 >= num2) {                  /* Work down from the highest base power to derive the base value */
        remainder = num1 % num0;
        quotient  = quotient / num0;
        cnv[i]    = derive(quotient);
        quotient  = remainder;
        num0 = num0 / num2;
        i  ;
    }

    cnv[i] = derive(remainder);             /* Finish by storing the "units" value */

    printf("%s\n", cnv);

    return 0;
}

A few bits of information to point out as to what I did to make the base conversion function.

  • Instead of utilizing the power function within the "math.h" include file, the program just keeps multiplying the base value by itself until the largest power of the base is reached that is less than or equal to the number being evaluated.
  • Once the largest power is determined, that value is used to derive each digit value by performing an integer division and retaining the remainder for subsequent divisions by decreasing powers of the base number.
  • Each quotient value is converted to its equivalent digit in the base and then stored in a character array and the divisor is divided by the base number until the divisor value is less than the base value.
  • Once all division has occurred within the "while" loop, the final remainder value is converted to the final digit and placed into the character array and the character array is then printed presenting the converted value.

So instead of working up with the power function, this method derives the needed largest power of the base value needed and works its way back down through the division and decrement process.

Testing out the program against the original values presented in the question produced the following terminal output.

@Vera:~/C_Programs/Console/Base/bin/Release$ ./Base 42 2
101010
@Vera:~/C_Programs/Console/Base/bin/Release$ ./Base 99 5
344

Anyway, you might want to evaluate this refactored version along with the other presented solutions to see if it meets the spirit of your project.

  •  Tags:  
  • c
  • Related