Home > OS >  How to not count 1.0 as an integer
How to not count 1.0 as an integer

Time:12-12

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!
  • Related