So I need to read an integer from the stdin where the user may input 1.0, however since this is a double I wouldn't want to accept it. However when I try the method below the 1.0 is converted to 1 and is accepted. I would also like to accept 0001 as a possible integer input as 1.
first_sentence_to_switch = 0;
char buf[15]; // large enough
int number;
wrong_input = 0;
scanf("s", buf); // read everything we have in stdin
// printf("buffer: %s", buf);
if (sscanf(buf, "%d", &number) == 1)
{
first_sentence_to_switch = number;
}
else
{
wrong_input = 1;
}
CodePudding user response:
You can use the %n format option to tell how much was matched by an sscanf call to make sure there is no extra cruft on the line:
if (sscanf(buf, "%d %n", &number, &end) == 1 && buf[end] == 0) {
.. ok
} else {
.. not an integer or something else in the input (besides whitespace) after the integer
Note the space between the %d
and %n
to skip any whitespace that might exist at the end of the buffer (such as a newline if the input was read by fgets or getline)
CodePudding user response:
How to read a whole line of input
The line
scanf("s", buf);
will never read a whole line of input. It will only read a single word of input (which can also consist of digits). For example, if the user enters invalid input such as
"39 jdsuoew"
on a single line, then it will only read the word "39"
as input, leaving the rest of the line on the input stream. This means that your program will accept the input as valid, although it should probably be rejected in this case.
Even if the user only entered "39"
, then it will only read this number, but will leave the newline character on the input stream, which can cause trouble.
If you want to ensure that it reads the entire line, I recommend that you use the function fgets
instead, as that function will always read a whole line of input (including the newline character), assuming that the size of the provided memory buffer is large enough to store the entire line.
char line[100];
//attempt to read one line of input
if ( fgets( line, sizeof line, stdin ) == NULL )
{
fprintf( stderr, "Input error!\n" );
exit( EXIT_FAILURE );
}
//search for newline character, to verify that entire line was read in
if ( strchr( line, '\n' ) == NULL )
{
fprintf( stderr, "Line was too long for input buffer!\n" );
exit( EXIT_FAILURE );
}
Note that the function strchr
requires that you #include <string.h>
. If, as you state in the comments section, you are not allowed to use that header file, then you will probably have to assume that the memory buffer was large enough for the entire line, without verifying it (which you are also doing in your code). Although it is possible to verify this without using the function strchr
, I don't recommend doing this. If the buffer is made large enough, then it is unlikely (but still possible) for the line to not fit into the buffer.
Convert string to integer using strtol
After reading the input line into a memory buffer, you can either use the function sscanf
or strtol
to attempt to convert the integer to a number. I recommend that you use the function strtol
, because the function sscanf
has undefined behavior if the user enters a number that is too large to be represented as a long int
, whereas the function strtol
is able to report such an error condition reliably.
In order to convert the line that you read to an integer, you could simply call strtol
like this:
long l;
l = strtol( line, NULL, 10 );
However, calling the function with the second argument set to NULL
has the same problem as calling the function atoi
: You have no way of knowing whether the input was successfully converted, or if a conversion error occured. And you also have no way of knowing how much of the input was successfully converted, and whether the conversion failed prematurely, for example due to the user entering the decimal point of a floating-point number.
Therefore, it is better to call the function like this:
long l;
char *p;
l = strtol( line, &p, 10 );
Now, the pointer p
will point to the first character that was not successfully converted to a number. In the ideal case, it will be pointing to the newline character at the end of the line (or maybe the terminating null character if you are not using fgets
). So you could verify that the whole line was converted, and that at least one character was converted, like this:
if ( p == line || *p != '\n' )
{
printf( "Error converting number!\n" );
exit( EXIT_FAILURE );
}
However, this is maybe a bit too strict. For example, if the user enters "39 "
(with a space after the number), the input will be rejected. You probably would want to accept the input in this case. Therefore, instead of requiring that p
is pointing to the newline character and thereby not accepting any other remaining characters on the line, you may want permit whitespace characters to remain in the line, like this:
if ( p == line )
{
printf( "Error converting number!\n" );
exit( EXIT_FAILURE );
}
while ( *p != '\n' )
{
//verify that remaining character is whitespace character
if ( !isspace( (unsigned char)*p ) )
{
printf( "Error converting number!\n" );
exit( EXIT_FAILURE );
}
p ;
}
Note that you must #include <ctype.h>
in order to use the function isspace
.
Also, as previously stated, the advantage of using the function strtol
over sscanf
is that it can reliably report whether the number is too large or too small to be representable as a long int
. If such an error condition occurs, it will set errno
to ERANGE
. Note that you must #include <errno.h>
in order to use errno
.
long l;
char *p;
errno = 0; //make sure that errno is not already set to ERANGE
l = strtol( line, &p, 10 );
if ( errno == ERANGE )
{
printf( "Number out of range!\n" );
exit( EXIT_FAILURE );
}
Code example of fgets
and strtol
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
int main( void )
{
char line[100], *p;
long l;
//prompt user for input
printf( "Please enter an integer: " );
//attempt to read one line of input
if ( fgets( line, sizeof line, stdin ) == NULL )
{
fprintf( stderr, "Input error!\n" );
exit( EXIT_FAILURE );
}
//search for newline character, to verify that entire line was read in
if ( strchr( line, '\n' ) == NULL )
{
fprintf( stderr, "Line was too long for input buffer!\n" );
exit( EXIT_FAILURE );
}
//make sure that errno is not already set to ERANGE
errno = 0;
//attempt to convert input to integer
l = strtol( line, &p, 10 );
//verify that conversion was successful
if ( p == line )
{
printf( "Error converting number!\n" );
exit( EXIT_FAILURE );
}
//check for range error
if ( errno == ERANGE )
{
printf( "Number out of range!\n" );
exit( EXIT_FAILURE );
}
//verify that there are either no remaining characters, or that
//all remaining characters are whitespace characters
while ( *p != '\n' )
{
//verify that remaining character is whitespace character
if ( !isspace( (unsigned char)*p ) )
{
printf( "Error converting number!\n" );
exit( EXIT_FAILURE );
}
p ;
}
//print valid input
printf( "Input is valid.\nYou entered: %ld\n", l );
}
This program has the following output:
Valid input:
Please enter an integer: 39
Input is valid.
You entered: 39
Junk after valid input on same line:
Please enter an integer: 39 jdsuoew
Error converting number!
Attempt to enter floating-point number instead of integer:
Please enter an integer: 1.0
Error converting number!
Attempt to enter number that is so large that it is not representable as a long int
:
Please enter an integer: 10000000000000000000000000
Number out of range!