#1
  1. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Dec 2012
    Location
    Manila, Philippines
    Posts
    64
    Rep Power
    5

    Why is the wrong input being accepted?


    Code:
    #include <stdio.h>
    
    int main (void)
    {
    	int num, i;
    	
    	do
    	{
    		printf ("How many numbers do you want to see? ");
    		scanf (" %i", &num);
    		if (num > 10)
    		{
    			printf ("The number cannot be greater than 10!\n\n");
    		}
    	}
    	while (num > 10);
    	
    	printf ("\n");
    
    	for (i = 1; i < 11; i++)
    	{
    		printf ("Counting up... %i\n", i);
    		if (i == num)
    		{
    			break;
    		}
    	}
    
    	printf ("\nThe rest of the program follows.");
    
    	return 0;
    }
    The program is supposed to accept integers 1-10 and will ask for input again if num > 10. However, when a user enters an 11-digit number (e.g. 55555555555), which should be greater than 10, the program still accepts it and starts counting. :confused:
    Last edited by kathyrollo; May 7th, 2013 at 09:17 PM.
  2. #2
  3. Contributing User
    Devshed Supreme Being (6500+ posts)

    Join Date
    Jan 2003
    Location
    USA
    Posts
    7,145
    Rep Power
    2222
    First, what compiler are you using? Is it a 16-bit or a 32-bit compiler? The importance of that is that that will determine the size of an int and hence the valid range of values that an int can hold.

    Second, have you printed out what your program stores when you enter that invalid input?

    Converting 55555555555 to hex so that we can see what's going on, we get 0xCEF5E80E3, which is 36-bit value. If you have a 32-bit int, then you lose that most significant hex digit, leaving you with 0xEF5E80E3. If that were an unsigned int, its value would be 4,015,948,003, but you declared it as a signed int. The greatest value that a 32-bit signed int can store is 2,147,483,647. So because the most-significant bit, which is the sign bit, is set to 1, this is a negative value. All negative values, regardless of the magnitude of their absolute value, are less than 10.

    Similarly if you have a 16-bit compiler, then that invalid input of 55555555555 would be stored as 0x80E3, which would be 32,995. The greatest signed value that a 16-bit int can store is 32,767. Again, because the sign bit is set to one, 0x80E3 will be interpreted as a negative number and, again, any and all negative numbers are less than 10.

    Display the value that your program thinks that it had just read in to verify that. I keep referring to 55555555555 as an "invalid input" because that's exactly what it is, invalid. It is invalid because it exceeds the range of values that your program can deal with.
  4. #3
  5. Banned ;)
    Devshed Supreme Being (6500+ posts)

    Join Date
    Nov 2001
    Location
    Woodland Hills, Los Angeles County, California, USA
    Posts
    9,607
    Rep Power
    4247
    Along with checking if the number is > 10, you should also check if the number is 0 or negative.

    Also, in your second loop, instead of counting from 1 to 10, why not just count from 1 to num. That way you don't need the code to break out of the loop (the entire i == num block)
    Up the Irons
    What Would Jimi Do? Smash amps. Burn guitar. Take the groupies home.
    "Death Before Dishonour, my Friends!!" - Bruce D ickinson, Iron Maiden Aug 20, 2005 @ OzzFest
    Down with Sharon Osbourne

    "I wouldn't hire a butcher to fix my car. I also wouldn't hire a marketing firm to build my website." - Nilpo
  6. #4
  7. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Dec 2012
    Location
    Manila, Philippines
    Posts
    64
    Rep Power
    5
    I read on type specifiers, changed it to long int num, and also included action for 0 and negative numbers as suggested. It will now just print a message for invalid input altogether. Here is the updated code:
    Code:
    // Fixed code
    
    #include <stdio.h>
    
    int main (void)
    {
    	int i;
    	long int num;
    	
    	do
    	{
    		printf ("Choose from 1 to 10.\nHow many numbers do you want to see? ");
    		scanf (" %li", &num);
    		if (num <= 0 || num > 10)
    		{
    			printf ("Your input is invalid!\n\n");
    		}
    	}
    	while (num <= 0 || num > 10);
    	
    	printf ("\n");
    	
    	for (i = 1; i < 11; i++)
    		{
    			printf ("Counting up... %i\n", i);
    			if (i == num)
    			{
    				break;
    			}
    		}	
    		
    	printf ("\nThe rest of the program follows.");
    
    	return 0;
    }
    Originally Posted by Scorpions4ever
    That way you don't need the code to break out of the loop (the entire i == num block)
    This is actually an exercise on the break; statement from the book I'm reading. Here is the original code:
    Code:
    // Absolute Beginner's Guide to C, 2E (1994)
    // By Greg Perry
    
    #include <stdio.h>
    
    int main (void)
    {
    	int num, i;
    	
    	printf ("How many numbers do you want to see? ");
    	scanf (" %i", &num);
    		
    	for (i = 1; i < 10; i++)
    	{
    		printf ("Counting up... %i\n", i);
    		if (i == num)
    		{
    			break;
    		}
    	} 
    	
    	return 0;
    }
    I tried experimenting on it and adding some features.

    Thank you very much for the input dwise1_aol and Scorpions4ever. :)
    Last edited by kathyrollo; May 7th, 2013 at 09:17 PM.
  8. #5
  9. Contributing User
    Devshed Supreme Being (6500+ posts)

    Join Date
    Jan 2003
    Location
    USA
    Posts
    7,145
    Rep Power
    2222
    kathyrollo, try entering this number: 4294967301
    Does the corrected program accept or reject it? BTW, in hex that number is 0x100000005, so I predict that your program will read it as 5.

    Also, read up on the format flags for scanf. You also need to match the size of the variable. Just changing the declaration of num is not enough, you also need to change scanf.
  10. #6
  11. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Dec 2012
    Location
    Manila, Philippines
    Posts
    64
    Rep Power
    5
    Changed to %li, thanks.

    Indeed, sir. The input is being accepted and read as 5. Hmmm... Now how do I stop this from happenning... :confused:
    Last edited by kathyrollo; May 7th, 2013 at 01:40 AM.
  12. #7
  13. Contributing User
    Devshed Supreme Being (6500+ posts)

    Join Date
    Jan 2003
    Location
    USA
    Posts
    7,145
    Rep Power
    2222
    That is a good question. What's happening is that you're experiencing overflow, which means that the value being assigned exceeds the range that can be held by that variable. When that happens, the variable holds the lower-order bits that it can hold and the higher-order bits are lost. And the value that you end up with is not what you expect it to be. It gets interesting when a signed positive value overflows into the sign bit such that you suddenly go from a large positive value to a negative value.

    Since I work mostly in embedded programming and a bit of GUI development, I haven't had to solve this kind of input problem yet. I believe that this is a very good problem for us to address and I would like to see that discussion. I would think that reading the entire line in with fgets(stdin) and then processing it, possibly with sscanf --the string version of scanf -- , would be involved.
  14. #8
  15. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Dec 2012
    Location
    Manila, Philippines
    Posts
    64
    Rep Power
    5
    Code:
    // Program to illustrate the && logical operator
    
    #include <stdio.h>
    
    int main (void)
    {
    	char lastName[25];
    	
    	printf ("What is your last name? ");
    	scanf (" %s", lastName);
    	
    	if ((lastName[0] >= 'P' && lastName[0] <= 'S') ||
    		(lastName[0] >= 'p' && lastName[0] <= 's'))
    	{
    		printf ("You must go to room 2432 for your tickets.");
    	}
    	else
    	{
    		printf ("You can get your tickets here.");
    	}
    	
    	return 0;
    }
    I know the code above could be written in much better ways. I've only learned character arrays for now, but I'm thinking if I can apply the same idea to my counting code for integers?

    If the first digit (index 0) is 0, it will print invalid input. Is there a way to do that, sir?
  16. #9
  17. Contributing User
    Devshed Supreme Being (6500+ posts)

    Join Date
    Jan 2003
    Location
    USA
    Posts
    7,145
    Rep Power
    2222
    I'm not sure what you're suggesting/asking there.

    Here's a modification of your program with attempts to test for integer overflow. errno.h contains the errno global variable that library functions will set when they encounter errors: zero means no error, non-zero means an error as #define'd in errno.h -- the error names are fairly standard, though their values may not be. To test for an error in a library function call, you set errno to zero, make the function call, then check the value of errno after the call. For example, according to scanf's man page (eg, http://linux.die.net/man/3/sscanf), one of the errors that could be set is ERANGE:
    ERANGE

    The result of an integer conversion would exceed the size that can be stored in the corresponding integer type.

    Conforming To

    The functions fscanf(), scanf(), and sscanf() conform to C89 and C99 and POSIX.1-2001. These standards do not specify the ERANGE error.
    So then the error we would expect and want to find in our case would be ERANGE. However, it appears that not every compiler's scanf can be expected to detect an ERANGE error.

    Here is my test program:
    Code:
    // More on break statements ver. 2
    
    #include <stdio.h>
    #include <limits.h>
    #include <errno.h>
    
    int main (void)
    {
        int num;
        int result;
        
        // test for excessively large input; ie,  > 2147483647 (0x7FFFFFFF, AKA INT_MAX)
        printf ("Enter input: ");
        errno = 0;
        result = scanf (" %i", &num);
        printf("errno = %d, result = %d, num = %d\n", errno, result, num);
    
        // test for detection of integer overflow
        num = INT_MAX;
        printf("num = %d\n", num);
        errno = 0;
        num += 42;
        printf("num+42 = %d; errno = %d\n", num, errno);
        
        return 0;
    }
    Here is what it outputs when I enter your test value:
    C:TEST>a
    Enter input: 55555555555
    errno = 0, result = 1, num = -279019293
    num = 2147483647
    num+42 = -2147483607; errno = 0

    C:TEST>
    So with my compiler, MinGW gcc, scanf does not detect ERANGE. I believe Orwell, which you are using, also uses the same compiler but very likely a later version, so you might want to compile and test this program with your compiler to see whether you get the same results. Read the errno.h header file to match the error number to the name.

    Note also that the overflow by addition also does not get trapped out as an integer overflow. Plus, this is an example where overflowing a positive signed value will cause a negative result.

    Google'ing a bit, I read that the handling of integer overflow is not defined in C and so will usually just silently happen.

    Suggestions that I've seen in a brief search (I'm at work on my lunch break now, so I don't have much time) are that you need to test for an overflow before it happens, meaning that you check the values to see whether the operation you're about to will likely result in an overflow. For example, that last one could have been trapped with (writing this untested on the spur of the moment):
    Code:
        // test for detection of integer overflow
        num = INT_MAX;
        printf("num = %d\n", num);
        errno = 0;
        if ( (INT_MAX - num) < 42)
            printf("OVERFLOW:  num + 42 will result in an overflow\n");
        else
            num += 42;
        printf("num+42 = %d; errno = %d\n", num, errno);
    OK, so I played with that a bit and it does seem to work.

    So one approach to your input problem could be to read in the entire input line with fgets(stdin) -- not with gets, which is unsafe -- and first count the number of digits in that number. If there are too many digits, then the number is too big, something you can test for without ever calling sscanf.

    The thing is that validating input and gracefully handling anything at all that the user could throw at your program is the standard that we need to work to. That is not a trivial task. And as someone once remarked, it's a situation in which we programmers are trying to write better and better idiot-proof programs while the Universe is creating better and better idiots; so far the Universe is winning.

    I would still really love to see other experienced programmers offer their approaches. Like I already said, this is an area that I've not had to work in for the past couple decades. Indeed, almost all my experience with scanf has been on this forum.
    Last edited by dwise1_aol; May 7th, 2013 at 02:08 PM.
  18. #10
  19. Banned ;)
    Devshed Supreme Being (6500+ posts)

    Join Date
    Nov 2001
    Location
    Woodland Hills, Los Angeles County, California, USA
    Posts
    9,607
    Rep Power
    4247
    You can also use fgets() and strtol(). The nice thing with this approach is that if the user enters "123abc", this will properly detect that there are letters in there, whereas scanf() will read 123 and leave the rest behind in the input buffer for another function to process.

    Code:
    #include <stdio.h>
    #include <stdlib.h>
    #include <limits.h>
    #include <errno.h>
    
    int main(void) {
        char buf[100];
        long l;
        char *ptr;
        printf("Enter a number: ");
        fgets(buf, sizeof(buf), stdin);
        l = strtol(buf, &ptr, 10);
        if (*ptr != '\0') {
            /* Error here. Invalid characters in the string */
        }
        if ( (l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) {
            /* Error here, out of range */
        }
        /* Add code here to check if l is within INT_MIN and INT_MAX or
            whatever range you wish to check */
    
        return 0;
    }
    The key here is how strtol() works:
    long int strtol(const char *nptr, char **endptr, int base);
    ...
    ...
    If endptr is not NULL, strtol() stores the address of the first invalid character in *endptr. If there were
    no digits at all, strtol() stores the original value of nptr in *endptr (and returns 0). In particular, if
    *nptr is not '\0' but **endptr is '\0' on return, the entire string is valid.
    ...
    ...
    RETURN VALUE
    The strtol() function returns the result of the conversion, unless the value would underflow or overflow. If
    an underflow occurs, strtol() returns LONG_MIN. If an overflow occurs, strtol() returns LONG_MAX. In both
    cases, errno is set to ERANGE.
    Up the Irons
    What Would Jimi Do? Smash amps. Burn guitar. Take the groupies home.
    "Death Before Dishonour, my Friends!!" - Bruce D ickinson, Iron Maiden Aug 20, 2005 @ OzzFest
    Down with Sharon Osbourne

    "I wouldn't hire a butcher to fix my car. I also wouldn't hire a marketing firm to build my website." - Nilpo
  20. #11
  21. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Dec 2012
    Location
    Manila, Philippines
    Posts
    64
    Rep Power
    5
    Hi guys,

    Ran the codes using Orwell Dev-C++ 5.4.2 RC6:
    dwise1_aol solution:
    Code:
    http://i.imgur.com/MHpUYgb.png
    Scorpions4ever solution:
    Code:
    http://i.imgur.com/2sXd8Z1.png
    Is this the expected output?
  22. #12
  23. Contributing User
    Devshed Supreme Being (6500+ posts)

    Join Date
    Jan 2003
    Location
    USA
    Posts
    7,145
    Rep Power
    2222
    I think so. scanf doesn't detect the overflow, so my example doesn't work for us.

    Scorpions didn't have any code to run if an error was found and none if strtol succeeded. And I don't know what outputs "TEST". It should detect the overflow, but we can't tell here. I think you should print out the values of l and of errno, which should tell us more of what's going on.

IMN logo majestic logo threadwatch logo seochat tools logo