The %d
conversion specifier tells scanf
to read and discard any leading whitespace, then read up to the first non-digit character (anything that isn't in the range ['0'-'9']
.
Assume your input stream contains the sequence:
{'1', ' ', '1', '.', '5', '\n'}
the first %d
reads first '1'
, stops reading at the space, leaving it in the input stream:
{' ', '1', '.', '5', '\n'}
then converts and assigns 1
to a
.
The second %d
reads and discards the space, reads the second '1'
and stops reading at the .
, leaving it in the input stream:
{'.', '5', '\n'}
then converts and assigns 1
to b
.
As far as scanf
is concerned it successfully read two integer inputs. The %d
conversion specifier doesn't know or care that 1.5
is a valid floating-point constant; it only cares that '.'
is a non-digit character, so it stops reading at that point.
Same thing if you enter something like 12s4
and read it with %d
-- scanf
will successfully convert and assign the 12
and leave s4
to foul up the next read.
There are several ways around this, none of them terribly elegant. My preferred method is to read the input as a string with fgets
, then tokenize and convert using strtol
for integers and strtod
for floats:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
/**
* This program demonstrates some of the gymnastics you have to go through
* to validate numeric input.
*
* Validation steps:
*
* 1. Make sure the fgets operation did not see an EOF or
* input error;
*
* 2. Make sure there's a newline character in the input
* buffer, otherwise the input was too long;
*
* 3. Make sure the strtol operation didn't over/underflow;
*
* 4. Make sure that the first character not converted by
* strtol is either whitespace or a terminator;
*
* 5. Make sure the value returned by strtol (which is a long) can be
* stored in a 32-bit int.
*/
int main( void )
{
/**
* Input buffer
*
* Using fixed-size buffers is always risky, but for the purpose of this
* demonstration should be good enough. A 32-bit integer can represent
* up to 10 decimal digits, plus sign, so to store two integer inputs
* we need a buffer that's *at least* 25 elements wide (two integers
* plus space plus newline plus terminator); rounding up
* to the next power of 2.
*/
char buf[32] = {0};
printf( "Gimme two integer values: " );
if ( !fgets( buf, sizeof buf, stdin ) )
{
if ( feof( stdin ) )
fprintf( stderr, "EOF signaled on standard input...\n" );
else
fprintf( stderr, "Error on standard input...\n" );
return EXIT_FAILURE;
}
if ( !strchr( buf, '\n' ) )
{
fprintf( stderr, "Input too long\n" );
return EXIT_FAILURE;
}
int a = 0, b = 0;
char *chk = NULL;
static const char *whitespace = " \n\r\t\f";
/**
* Break the input into tokens separated by whitespace, then
* attempt to convert each token to an integer with strtol.
* The chk parameter will point to the first character *not*
* converted; if that character is anything other than whitespace
* or a terminator, then the input is not a valid integer.
*/
for ( char *tok = strtok( buf, whitespace ), cnt = 0; tok != NULL && cnt < 2; tok = strtok( NULL, whitespace ), cnt++ )
{
errno = 0;
long tmp = strtol( tok, &chk, 10 );
if ( !(isspace( *chk ) || *chk != 0) )
{
fprintf( stderr, "\"%s\" is not a valid integer, exiting...\n", tok );
return EXIT_FAILURE;
}
else if ( (tmp == LONG_MIN || tmp == LONG_MAX) && errno == ERANGE )
{
fprintf( stderr, "Overflow detected while converting \"%s\" to long, exiting...\n", tok );
return EXIT_FAILURE;
}
else if ( tmp > INT_MAX || tmp < INT_MIN )
{
fprintf( stderr, "\"%s\" cannot be represented in an int...\n", tok );
return EXIT_FAILURE;
}
else if ( cnt == 0 )
{
a = tmp;
}
else
{
b = tmp;
}
}
printf( "a = %d, b = %d\n", a, b );
return EXIT_SUCCESS;
}
Yeah. Welcome to programming in C. And this code has some severe weaknesses; strtok
is destructive and non-threadsafe, using fixed-size buffers is always risky, I'm relying on long
having greater range than int
(not necessarily true on some systems), etc.
Some sample runs:
% ./input5
Gimme two integer values: ^D EOF signaled on standard input...
% ./input5
Gimme two integer values: 1234567890123456789012345678901234567890
Input too long
% ./input5
Gimme two integer values: 1234567890123456789012345
Overflow detected while converting "1234567890123456789012345" to long, exiting...
% ./input5
Gimme two integer values: 12345678901234
"12345678901234" cannot be represented in an int...
% ./input5
Gimme two integer values: 123 4.56
"4.56" is not a valid integer, exiting...
% ./input5
Gimme two integer values: 123 456
a = 123, b = 456