Why does scanf fail with floats?

When I write this, compile it, and run:

int x;   
scanf ("%d", &x);  
while (x!=4) {  
    scanf ("%d", &x);  
}

then when inserting char or double number less than 4 it enter an infinite loop. When inserting double greater than 4 it terminates.

Any explanation for this?


Solution 1:

From the C language standard (n1256):

7.19.6.2 The fscanf function
...
4 The fscanf function executes each directive of the format in turn. If a directive fails, as detailed below, the function returns. Failures are described as input failures (due to the occurrence of an encoding error or the unavailability of input characters), or matching failures (due to inappropriate input).
...
7 A directive that is a conversion specification defines a set of matching input sequences, as described below for each specifier. A conversion specification is executed in the following steps:

8 Input white-space characters (as specified by the isspace function) are skipped, unless the specification includes a [, c, or n specifier.250)

9 An input item is read from the stream, unless the specification includes an n specifier. An input item is defined as the longest sequence of input characters which does not exceed any specified field width and which is, or is a prefix of, a matching input sequence.251) The first character, if any, after the input item remains unread. If the length of the input item is zero, the execution of the directive fails; this condition is a matching failure unless end-of-file, an encoding error, or a read error prevented input from the stream, in which case it is an input failure.

10 Except in the case of a % specifier, the input item (or, in the case of a %n directive, the count of input characters) is converted to a type appropriate to the conversion specifier. If the input item is not a matching sequence, the execution of the directive fails: this condition is a matching failure. Unless assignment suppression was indicated by a *, the result of the conversion is placed in the object pointed to by the first argument following the format argument that has not already received a conversion result. If this object does not have an appropriate type, or if the result of the conversion cannot be represented in the object, the behavior is undefined.

Emphasis added in paragraph 10. The %d conversion specifier expects the input text to be formatted as a decimal integer. If it isn't, the conversion fails and the character that caused the conversion to fail is left in the input stream. Further calls to scanf() with the %d conversion specifier will choke on the same character.

scanf() returns the number of successful assignments; you need to check this result to see if the conversion succeeded, like so:

int x = 0;
while (x != 4)
{
  int result = scanf("%d", &x);
  if (result != 1)
  {
    printf("Last call to scanf() failed; exiting\n");
    break;
  }
}

Unfortunately, you still have the bad input stuck in the input stream. There are a number of strategies for dealing with this. You could remove the offending character with getchar and try again:

while (x != 4)
{
  int tmp;
  if (scanf("%d", &tmp) == 0)
    getchar();
  else
    x = tmp;
}

Or you could try to read up to the next newline, assuming that all remaining input is b0rked:

while (x != 4)
{
  int tmp;
  if (scanf("%d", &tmp) == 0)
    while (getchar() != '\n')
      ;
  else
    x = tmp;
}

Or you could try to read the input as text and convert to integer using strtol() (my preferred technique):

char input[SOME_SIZE];
int x = 0;
...
while (x != 4)
{
  if (fgets(input, sizeof input, stdin))
  {
    char *check;
    int tmp = (int) strtol(input, &check, 10);
    if (!isspace(*check) && *check != 0)
    {
      printf("%s is not a valid integer: try again\n", input);
    }
    else
    {
      x = tmp;
    }
  }
  else
  {
    printf("Read error on standard input\n");
    break;
  }
}

It's more work, but it allows you to catch bad input before it gets assigned to x.

Solution 2:

You don't check if the scanf actually succeeded, therefore you will get stuck on error. With each loop, the scanf will try to read and fail.

scanf returns the number of successfully read items therefore modify the loop to something like this while (x!=4) { if (scanf("%d",&x) != 1) break; }

Solution 3:

When scanf stops scanning at a specific position in the input stream, it will never advance the stream, so the next scanf will try again the same error ... and again ... and again

input: 42 23 foo ...
scanf: ^
x      42
scanf:   ^
x      23
scanf:      ^
x      unusable
scanf:       ^
x      unusable
scanf:       ^
x      unusable
scanf:       ^
x      unusable
scanf:       ^
x      unusable