Search code examples
cgetchar

C getchar input problems


I need to receive a single line of characters and convert them from the base they are written in to a decimal base, as soon as a character is typed.

I did this by using the following function:

int inToDec(int base) //My assumption is that Enter will not be pressed as the first character - if it is, I am not sure what to do in that case
{
    int numdec=0, c;//decimalized number, a variable for character inpu
    while((c = getchar()) != '\n' && c != EOF)
    {
        if(c >= 97)//Checks if the char is an actual char
            c = c - 'a' - 1;
        else//or a digit char
            c = c;
        numdec += numdec * base + c;//every time another char is inputted, it multiplies the numdec by the base and adds the char's value in decimal base - which is essentially the algorithm for conversion to decimal base.
    }
    return numdec;
}

My main():

#include <stdio.h>
#include "funcs.h"

int main()
{
    int option, fbase, tbase, num1, num2;
    do
    {
        scanf("%d",&option);
        switch(option)
        {
        case 1:
            scanf("%d",&fbase);
            num1 = inToDec(fbase);
            num2 = inToDec(fbase);
            outFromDec(num1+num2,fbase);
            break;
        case 2:
            scanf("%d",&fbase);
            num1 = inToDec(fbase);
            num2 = inToDec(fbase);
            if(num1>=num2)
                outFromDec(num1-num2,fbase);
            else
                outFromDec(num2-num1,fbase);
            break;
        case 3:
            scanf("%d",&fbase);
            scanf("%d",&tbase);
            num1 = inToDec(fbase);
            outFromDec(num1,tbase);
            break;
        }
    }while(option != 4);
    return 0;
}

Except that when I input the number(after the option and the desired base) and type Enter to continue to the next one, suddenly it starts to print a character time after time without end. What did I do wrong and how do I fix it? Or, if you do not want to explain some obvious fact to a noob, where do I read about it (preferably without too much heavy duty tech-talk, as I am relatively new to programming).

outFromDec() -

void outFromDec(int num, int base) //converts num into the base given and prints
{
    int count = 0 , temp = num, i;
    char c;
    while(temp != 0)
    {
        temp /= base;
        count++;
    }
    temp = num;
    do
    {
        for(i=1;i<count;i++)
            temp /= base;
        if(temp<=9)
            c = '0' + temp;
        else
            c = 'a' + temp - 1;
        putchar(c);
        count--;
        temp = num / temp;
    }while(temp != 0);
}

Solution

  • This code has problems:

    while(c = getchar() != '\n' && c != EOF)
    

    Not enough parentheses — assignment is a low priority operator. It should be:

    while ((c = getchar()) != '\n' && c != EOF)
    

    Another set of problems is in the conversion code in the body of the loop:

        if (c >= 97)
            c = c - 'a' - 1;
        else
            c = c;
        numdec += numdec * base + c;
    

    The choice of 97 is presumably the ASCII or ISO 8859-n or Unicode code point for a. This ignores upper case letters, punctuation, and treats the digits 0 to 9 as though they 48..57. You probably need to use #include <ctype.h>. It also does not validate that the 'digit' is valid for the base.

    However, on the first call to intToDec(), the first character read is a newline, left over by scanf(), so the first number is always zero, if you enter the numbers one per line as you're told to.

    When you finally get to outToDec(), you have some interesting numerology. I added printf() statements to track function entry and exit, and also for key points in the loop:

    void outFromDec(int num, int base)
    {
        printf("-->> %s: %d %d\n", __func__, num, base);
        int count = 0, temp = num, i;
        char c;
        while (temp != 0)
        {
            temp /= base;
            count++;
        }
        printf("Count: %d\n", count);
        temp = num;
        do
        {
            printf("count: %d; temp = %d\n", count, temp);
            for (i = 1; i < count; i++)
                temp /= base;
            if (temp <= 9)
                c = '0' + temp;
            else
                c = 'a' + temp - 1;
            putchar(c);
            count--;
            temp = num / temp;
        } while (temp != 0);
        printf("<<-- %s\n", __func__);
    }
    

    The __func__ is a predefined identifier in C99 containing the function name. It may not be available to you in MSVC; if not, replace it with the function name.

    For the inputs 1, 9, 9, the output from the program was:

    -->> inToDec: 9
    <<-- inToDec: 0
    -->> inToDec: 9
    <<-- inToDec: 57
    -->> outFromDec: 57 9
    Count: 2
    count: 2; temp = 57
    6count: 1; temp = 9
    9count: 0; temp = 6
    6count: -1; temp = 9
    9count: -2; temp = 6
    

    And the count continued to decrease and the 6's and 9's continued to alternate. You seem to be trying to isolate the digits with the most significant digit (MSD) discovered first. The loop determining count is correct; the loop printing the digits clearly isn't. You should be able to take it from there; it is routine debugging. Note how I used the print statements to see what was going on. If you can't resolve it by just looking at the code, print out the result of every expression, if necessary.

    I observe that to print 57 in base 10, you'd discover that there are 2 digits to print (count == 2). The first digit would be found by dividing by the base (10) count-1 times; this would give you 5 to print. You probably need to subtract 5 * 10 from the number so that the next (in this case, the last) time around the loop, you'd start with just 7, which you'd print. The loop would stop. You should ensure the loop breaks if count ever goes negative.

    This is an expensive way of formatting a 10-digit 32-bit number (or even more so a 19-digit 64-bit number). It can be made to work, though. The standard procedure collects the digits in reverse order and arranges to print them out in the inverse order. (number % base gives you the digit to be printed; number /= base reduces the number of digits left to process.)


    As happens quite often, the OP is under artificial constraints and may not use strings. Yuck!

    Here is a 'read an integer' function that is plausibly robust. It does assume 2's complement arithmetic; it contains an assertion that should fire if it was ever run on a machine that was sign-magnitude or 1's complement (but I haven't tested it; I don't have any such machines available to test on).

    Note that the code accumulates the number as a negative number, and makes it positive at the end if it should be positive. This makes it easier to deal with INT_MIN than trying to accumulate it as a positive int.

    For the purposes of the exercise, I'm treating the system as though sizeof(intmax_t) == sizeof(int) (and hence sizeof(int) == sizeof(long) and sizeof(int) == sizeof(long long) too); this technique would work if the integer type were intmax_t instead of int. Note that the C standard does not rule out this supposed configuration (but the standard requires that CHAR_BIT * sizeof(int) >= 64 to be a conforming implementation).

    #include <assert.h>
    #include <ctype.h>
    #include <limits.h>
    #include <stdio.h>
    
    /* Read an integer from stdin without prompt: code is not allowed to use strings! */
    enum { E_OK = 0, E_EOF = -1, E_INVCHAR = -2, E_OVERFLOW = -3 };
    
    extern int read_an_int(int *value);
    
    int read_an_int(int *value)
    {
        int number = 0;
        int c;
        int pos_neg = +1;
    
        assert(-INT_MAX != INT_MIN);    // Probably 2's complement
    
        while ((c = getchar()) != EOF && isspace(c))
            ;
        if (c == '-')
        {
            pos_neg = -1;
            c = getchar();
        }
        else if (c == '+')
        {
            pos_neg = +1;
            c = getchar();
        }
        if (c == EOF)
            return E_EOF;
        if (!isdigit(c))
            return E_INVCHAR;
    
        number = '0' - c;   /* Negated digit */
    
        while ((c = getchar()) != EOF && isdigit(c))
        {
            int d = '0' - c; /* Negated digit */
            if (number < INT_MIN / 10 || (number == INT_MIN/10 && d < INT_MIN % 10))
                return E_OVERFLOW;
            //printf("N1 %d; d %d; ", number, d);
            number = number * 10 + d;
            //printf("N2 %d\n", number);
        }
        if (c != EOF)
            ungetc(c, stdin);
    
        if (pos_neg != -1)
        {
            //printf("Should be switched (%d)(%d)\n", pos_neg, number);
            if (number == INT_MIN)
                return E_OVERFLOW;
            number = -number;
            //printf("Should be positive (%d)(%d)\n", pos_neg, number);
        }
    
        *value = number;
        return E_OK;
    }
    
    static void gobble_input(void)
    {
        int c;
        while ((c = getchar()) != EOF)
        {
            if (isdigit(c) || c == '+' || c == '-')
            {
                ungetc(c, stdin);
                break;
            }
            printf("Skip %c\n", c);
        }
    }
    
    int main(void)
    {
        int rc;
        int number;
    
        while ((rc = read_an_int(&number)) != E_EOF)
        {
            switch (rc)
            {
            case E_INVCHAR:
                printf("Invalid character spotted\n");
                gobble_input();
                break;
            case E_OVERFLOW:
                printf("Input would have overflowed integer range %d..%d\n", INT_MIN, INT_MAX);
                break;
            case E_OK:
                printf("Input number: %d\n", number);
                break;
            default:
                assert(0);
                break;
            }
        }
    
        return 0;
    }
    

    The test data file I used was:

    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    11
    +123
    1234
    56789
    +123456789
    2147483647
    2147483648
    +000000123456789
    000000123456789
    -0000
    +0000
    -1
    -2
    -9
    -21
    -321
    -4321
    -2147483647
    -2147483648
    -2147483649
    # Bogus data or partially bogus data
    -
    +
    -213a
    +213a
    +.213
    3.14159E+23
    

    The output from that was:

    Input number: 0
    Input number: 1
    Input number: 2
    Input number: 3
    Input number: 4
    Input number: 5
    Input number: 6
    Input number: 7
    Input number: 8
    Input number: 9
    Input number: 11
    Input number: 123
    Input number: 1234
    Input number: 56789
    Input number: 123456789
    Input number: 2147483647
    Input would have overflowed integer range -2147483648..2147483647
    Input number: 123456789
    Input number: 123456789
    Input number: 0
    Input number: 0
    Input number: -1
    Input number: -2
    Input number: -9
    Input number: -21
    Input number: -321
    Input number: -4321
    Input number: -2147483647
    Input number: -2147483648
    Input would have overflowed integer range -2147483648..2147483647
    Invalid character spotted
    Skip  
    Skip B
    Skip o
    Skip g
    Skip u
    Skip s
    Skip  
    Skip d
    Skip a
    Skip t
    Skip a
    Skip  
    Skip o
    Skip r
    Skip  
    Skip p
    Skip a
    Skip r
    Skip t
    Skip i
    Skip a
    Skip l
    Skip l
    Skip y
    Skip  
    Skip b
    Skip o
    Skip g
    Skip u
    Skip s
    Skip  
    Skip d
    Skip a
    Skip t
    Skip a
    Skip 
    
    Invalid character spotted
    Invalid character spotted
    Input number: -213
    Invalid character spotted
    Skip 
    
    Input number: 213
    Invalid character spotted
    Skip 
    
    Invalid character spotted
    Input number: 213
    Input number: 3
    Invalid character spotted
    Input number: 14159
    Invalid character spotted
    Input number: 23
    

    Note that the last line provided 3 valid numbers (and two invalid characters, the . and the E).

    It isn't particularly easy; that's why these things are coded in library functions. Also, the 'no strings' requirement means I can't do decent error reporting when there are invalid characters or overflows.