Two things to remember about scanf
and the %d
conversion specifier:
%d
tells scanf
to skip over any leading whitespace, then read any decimal digit characters up to the first non-decimal digit character;
scanf
returns the number of items successfully converted and assigned.
So, here's how scanf( "%d", &input )
will behave based on different input:
Input text Value saved to input Return value
---------- -------------------- ------------
" abc" n/a 0
" 12c" 12 1
" 1b3" 1 1
" 123" 123 1
In all cases, the %d
conversion specifier tells scanf
to skip over leading whitespace.
If the first non-whitespace character entered is a non-digit character, scanf
will stop reading at that point, input
will not be updated, and it will return 0
.
If the first non-whitespace character is a decimal digit, scanf
will read up to the first non-decimal digit character, then convert and assign the resulting digit string to input
. Unfortunately, this won't help you catch cases like "12c"
or "1b3"
- as long as there's at least one decimal digit in the string, scanf( "%d", ... )
will treat it as a success and return 1
.
So, if you want to validate that your user has input a valid decimal integer string, you have to do two things:
- You have to check the return value of
scanf
;
- You have to check the first character following the end of the input to see whether or not it's whitespace.
There are a couple of ways to accomplish the second part. You can read the character immediately following what should be the decimal integer input - if it's anything other than whitespace, then the input isn't valid:
char dummy;
int itemsRead = scanf( "%d%c", &input, &dummy );
if ( itemsRead == 0 || itemsRead == 2 && !isspace( dummy ) )
{
fprintf( stderr, "Input is not valid, try again!\n" );
while ( getchar() != '\n' )
; // empty loop
}
If itemsRead
is 0
, then the first input character was not a decimal digit character, and the input is not good.
If itemsRead
is 2
and dummy
is not a whitespace character, then we have a situation like "12c"
or "1b3"
and the input is not good.
If the input is not good, then write an error message and scrub the input stream of any remaining characters up to the next newline character using getchar
.
If itemsRead
is 1
, then we saw EOF after nothing but decimal digit characters, so the input is good and there's nothing else to do. If itemsRead
is 2
and dummy
is whitespace, then the input is good and there's nothing else to do (I'm assuming you don't need to push whitespace back onto the input stream).
Alternately, you can read your input as text and use strtol
to convert it to an integer. strtol
will also set a pointer to point to the first character not converted:
#define MAX_INPUT_SIZE 11 // A 32-bit integer takes up to 10 decimal digits,
// plus a possible sign character.
char buffer[MAX_INPUT_SIZE + 1]; // +1 for string terminator
if ( fgets( buffer, sizeof buffer, stdin ) )
{
char *chk;
int tmp = (int) strtol( buffer, &chk, 10 ); // convert decimal digit string
// to an integer value
if ( isspace( *chk ) || *chk == 0 )
{
input = tmp;
}
else
{
fprintf( stderr, "%s is not a valid input, try again\n", buffer );
}
}
else
{
// error on input
}
You'll notice I assigned the result of strtol
to a temporary - it's a good idea to not update your target until after you've verified the input is good.