Search code examples
cscanf

Why doesn't scanf("%d %d") complain about decimal input?


Why when I run this code:

#include <stdio.h>

int main () {
    int a, b;
    if (scanf("%d %d", &a, &b) == 2)
        printf("%d %d", a, b);
    else 
        printf("Something went wrong");
    return 0;
}

and input for example:

1 1.5

output is:

1 1

Why scanf reads both numbers before '.' and ignores '.5'? How do you check that the last number is not a float and string ends?

OS: MacOS/linux Compiler: gcc

I just want runs something like above code

input:

1 1.5234

(some float number)

output:

Something went wrong

Solution

  • The short answer is that you can't really figure out what's going on with scanf. Its man page unhelpfully states:

    It is very difficult to use these functions correctly, and it is preferable to read entire lines with fgets(3) or getline(3) and parse them later with sscanf(3) or more specialized functions such as strtol(3).

    It is very easy for scanf (and fscanf) to get out of sync with the input. I use fgets and sscanf. With those I can greatly improve your input validation as follows:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main () {
        char iline[80];
        int a, b;
        char extra[2];
        int result;
    
        if (fgets(iline, sizeof(iline), stdin) == NULL) {
            printf("Need an input line.\n"); exit(1);
        }
    
        result = sscanf(iline, "%d %d%1s", &a, &b, extra);
        if (result == 2) {
            printf("%d %d\n", a, b);  // Success!
        } else  if (result == 3) {
            printf("Extra stuff starting at '%s'\n", extra);
        } else if (result < 2) {
            printf("Could not find two integers\n");
        }
        return 0;
    }
    

    Here's some test runs:

    $ gcc x.c
    $ echo "1" | ./a.out
    Could not find two integers
    $ echo "1 2" | ./a.out
    1 2
    $ echo "1 2.5" | ./a.out
    Extra stuff starting at '.'
    $ echo "1.5 2" | ./a.out
    Could not find two integers
    $ echo "1 2 5" | ./a.out
    Extra stuff starting at '5'
    

    By reading the line separately from scanning it, you can add the "extra" string to test if the scan prematurely ended before the line was consumed. If I use scanf in my code above, it will refuse to return until it actually finds something extra to scan.