2

I have read that the returned value of scanf indicates if it successfully was able to read and convert values. I have using the returned value to detect if the input is an integer or not but if the input was 4.5 scanf returns 1 while if the input was g scanf returns 0. Her's my code:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int x;
    int ReturnedValue=scanf("%d",&x);
    printf("Scanf Returned:%d",ReturnedValue);
    return 0;
}

in Multiple different articles you suggested to use the returned value of scanf to detect for integers but why this didn't work now?

John
  • 69
  • 3
  • 1
    because scanf could parse 4 out of your string. It's enough for scanf to be happy. – Jean-François Fabre Nov 27 '19 at 21:39
  • but how could I detect if it's integer or not in simple fast way without destroying my whole code in c? – John Nov 27 '19 at 21:40
  • Quote from the man page: *On success, these functions return the number of input items successfully matched and assigned; this can be fewer than provided for, or even zero, in the event of an early matching failure.*. The input `4.5` would match the 4 and assign it to `&x` and leave `.5` behind in the buffer. Hence it returns 1. – Pablo Nov 27 '19 at 21:40
  • 1
    "but how could I detect if it's integer or not in simple fast way without destroying my whole code in c?" `scanf` is the wrong function for that. Better read the whole line with `fgets` and parse it using better functions like `strtol`, `stroul`, etc. See Jean-François Fabre's answer. – Pablo Nov 27 '19 at 21:43
  • *but how could I detect if it's integer or not in simple fast way?* By **not using `scanf`**. There are a few (a very few) things `scanf` is good for, but reading integers *and* accurately complaining if non-integer input is received is simply not one of them. Be nice to yourself: don't waste any more time on `scanf`; [learn about some better alternatives](https://stackoverflow.com/questions/58403537/what-can-i-use-to-parse-input-instead-of-scanf). – Steve Summit Nov 28 '19 at 00:01

5 Answers5

5

The return value is not signaling success or failure. It is the number of successfully converted values.

That's why you got "1" on "4.5" with "%d" as format, the "4" was converted.

You would have found this by reading the documentation of scanf().

the busybee
  • 5,650
  • 2
  • 9
  • 25
3

Problem is that scanf (or atoi) aren't really reliable to parse integers.

In your case, scanf finds 4 and is happy with that, which explains the 1 return code.

A safest way is to use the strtol, strtoul .. primitives that make sure that the word is valid as an integer (for more details check How to scanf only integer? where the answer is "don't use scanf" BTW :))

In your particular case (stuck with scanf), you could make input more robust by scanning as float first (using %f on a float variable). And checking if the floating point value is an integer.

float xf;
if (scanf("%f",&xf) == 1)
{
     // one integer or float was scanned
     x = (int)xf;
     if (x==xf)
     {
        // scanned value is an integer: valid
     }
}

But inputs like 4XXXX would still be wrongly accepted. If you have to use scanf, then you have to accept a degree of risk.

Jean-François Fabre
  • 126,787
  • 22
  • 103
  • 165
  • How to check that input is an integer? – John Nov 27 '19 at 21:43
  • I am forced to use scanf in collage – John Nov 27 '19 at 21:44
  • maybe by first scanning with %f format to detect extra dot… edited. But still not the best solution. – Jean-François Fabre Nov 27 '19 at 21:44
  • 1
    @John sadly a lot of people use the `scanf` family of function incorrectly and teachers (and books and tutorial) make the mistake of teaching `scanf` as the way to read from the user. But `scanf` is not there to read random user data but they "scan[s] input according to [a] format". User input is random, can have errors, be incomplete and sometimes is very hard to find a good pattern to catch it all. Because these functions scan the input, when they fail, they leave stuff in the buffer behind which can be a big gotcha, when you don't know what your are doing... – Pablo Nov 27 '19 at 21:48
  • ok but how to detect if the number has dot? to me 2.0 is not an integer – John Nov 27 '19 at 21:49
  • 1
    My advice would be not to use `scanf` to read from the user but use `fgets` to get the whole line and the user better functions to parse the input. Bending `scanf` to accept/deny all kind of inputs will only end in frustration. – Pablo Nov 27 '19 at 21:49
  • 1
    @John use `strtol`. The second parameter of `strtol` is a pointer to a `char*` that is set to the first non-integer character in the stream. If the whole c-string is an integer, then deference to `'\0'`, something else otherwise. That's how you do it. – Pablo Nov 27 '19 at 21:51
  • If you simply *have* to use `scanf`, if some ninny has forbidden the use of superior alternatives like `fgets`, I suppose you could read lines using `scanf("%99[^\n]\n", linebuf);`, and then operate on `linebuf` as you would have done after calling `fgets`, if you were allowed to. – Steve Summit Nov 28 '19 at 00:06
  • 1
    *I am forced to use scanf in college.* If it were me, I would just ignore that requirement. `scanf` is square training wheels. No real C programmers use it for anything; its sole purpose is to frustrate beginners as they desperately try to use it to get numbers into their beginning programs to work on. The sooner you can abandon it and [move on to better alternatives](https://stackoverflow.com/questions/58403537/what-can-i-use-to-parse-input-instead-of-scanf), the better. – Steve Summit Nov 28 '19 at 00:08
0

I have using the returned value to detect if the input is an integer or not but if the input was 4.5 scanf returns 1 while if the input was g scanf returns 0.

That actually makes sense in this case.

int ReturnedValue=scanf("%d",&x);

Remember scanf return value is the number of items parsed correctly.

  • If you enter 4.5, it only reads the first digit 4 and interprets the int value correctly. It discards all the remaining invalid characters from the input.

  • If you enter d, then scanf fails completely (it cannot parse any int) and so it returns 0.

Refer to the scanf manual - to see why 4.5 is parsed as integer 4:

Reading of characters stops either when this maximum is reached or when a nonmatching character is found, whichever happens first.

http://man7.org/linux/man-pages/man3/scanf.3.html

artm
  • 16,141
  • 4
  • 27
  • 46
0

I initially didn't want to write an answer and left comments behind. I'd like to expand on Jean-François Fabre's answer.

The problem with the scanf family is that they don't do what most people believe they do and then they are left confused as to why it does something "strange". In reality they scan input based on an format and assign values to variables (passed via pointers). When they encounter an error, i.a. something doesn't match to the format, then they stop.

With the format "%d" you are just saying read an integer. The format doesn't say "read an integer and only an integer". To do that you would need to also exclude everything else and if I'm not mistaken it would be something like this "%d[^0-9]*" (I haven't tested it to see if I'm right). That's why the user input 4.5 is accepted by scanf. Note that scanf is not doing it incorrectly, you are specifying it incorrectly, because you assume, that "%d" means "and integer and integer only".

As you can see, it is pretty hard to come up with a format that catches everything and also tells you when the user made an error. User input aka what the user enters is random and can contain (unintentionally) errors, miss the format, etc. So you end up with very complex formats to just determine if the user added a dot at the end and fail if the user does that.

That's why my advice is to read the while input via fgets and then use something better to parse the input like strtol or even sscanf if you have a good format.

Here an example for the usage of strtol:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    const char *entries[] = { "4.5", "4", "some random text" };
    char *ptr;

    int i;
    for(i = 0; i < sizeof entries / sizeof *entries; ++i)
    {
        long int res = strtol(entries[i], &ptr, 0);

        if(*ptr != '\0') {
            printf("The input '%s' is not an integer. The non-integer is '%s'\n", entries[i], ptr);
        } else {
            printf("The input '%s' is an integer: %ld\n", entries[i], res);
        }
    }

    return 0;
}

which returns

The input '4.5' is not an integer. The non-integer is '.5'
The input '4' is an integer: 4
The input 'some random text' is not an integer. The non-integer is 'some random text'

This is a more precise way to check if the whole input is an integer or not.

Pablo
  • 12,254
  • 3
  • 31
  • 52
0

Scanf returns 1 instead of 0 when an error occurs

An error did not occur in input. The first character "4" converts nicely to a 4. Code needs to examine the rest of the line.

I am forced to use scanf in collage

Such coding constraints are unfortunate. fgets()/strtol() would be better.

how could I detect if it's integer or not in simple fast way without destroying my whole code in c?

Scan for the integer and look for trailing junk.

char endchar;  
int cnt = scanf("%d%c",&x, &endchar);
if (cnt == EOF) puts("End-of-file occurred");
else if (cnt == 1 || (cnt == 2 && endchar == '\n')) printf("Success %d\n", x);
else puts("Non-integer input");

When data is non-integer input, some offending characters may remain in stdin. As this task is only to read one int, that is OK.

Production code would more likely read a line and then parse it.

chux - Reinstate Monica
  • 113,725
  • 11
  • 107
  • 213