Nicholas Hassan
Nicholas Hassan

Reputation: 969

entering a string into scanf with a while < 0 condition causes infinite loop

I have a "minutes" variable that I'd like the user to enter a positive number into.

int main(void)
{
    float minutes;
    minutes = -1;
    printf("Find out how many bottles worth of water your showers use!\n");
    printf("How many minutes do you spend in the shower? ");
    scanf("%f", &minutes);
    while(minutes < 0)
    {
        printf("Please enter a positive number: ");
        scanf("%f", &minutes);
    }
}

It works as intended for numbers. If minutes >= 0, it accepts it, and if minutes < 0 it keeps asking. If I enter a string it infinitely loops

printf("Please enter a positive number: "); 

and never gives me a chance to enter a new value. Why is this and how can I fix it? Thanks!

Upvotes: 5

Views: 522

Answers (2)

dbush
dbush

Reputation: 223699

If you don't enter a numerical value, whatever you types stays in the input buffer. You can check for this by reading the return value of scanf, which tells you the number of items read. If it is 0, you can use getchar to read characters until the next newline to flush the buffer.

int main(void)
{
    int rval, c;
    float minutes;
    minutes = -1;
    printf("Find out how many bottles worth of water your showers use!\n");
    printf("How many minutes do you spend in the shower? ");
    rval = scanf("%f", &minutes);
    if (rval == 0) {
        while (((c = getchar()) != '\n') && (c != EOF));
    }
    while(minutes < 0)
    {
        printf("Please enter a positive number: ");
        rval = scanf("%f", &minutes);
        if (rval == 0) {
            while (((c = getchar()) != '\n') && (c != EOF));
        }
    }
}

Upvotes: 6

John Bode
John Bode

Reputation: 123458

The %f conversion specifier tells scanf to stop reading input as soon as it sees a character that isn't part of a legal floating-point constant (i.e., something that isn't a digit, decimal point, or sign). That bad character gets left in the input stream, so the next call to scanf fails, and the next, and the next, etc.

You should always check the return value of scanf - it will tell you how many items were successfully read and assigned from the input stream. In this case, you're expecting a single item, so you should get a return value of 1. If you get a return value of 0, then it means the input is not a proper floating point value, and that bad input has to be cleared somehow. Here's one possible solution:

if ( scanf( "%f", &minutes ) == 1 )
{
  // process minutes as normal
}
else
{
  // clear everything up to the next whitespace character
  while ( !isspace( getchar() ) )
    ; // empty loop 
}

The only problem with this is that scanf is kind of dumb, and if you type something like 123fgh, it will convert and assign the 123 while leaving fgh in the input stream; you would probably want to reject that whole input completely.

One solution is to read the input as text, and then convert it using strtod:

char buffer[BUFSIZE]; // where BUFSIZE is large enough to handle expected input
...
if ( fgets( buffer, sizeof buffer, stdin ) )
{
  char *chk; // chk will point to the first character *not* converted; if
             // it's anything other than whitespace or the string terminator,
             // then the input was not a valid floating-point value.
  double tmp = strtod( buffer, &chk );
  if ( isspace( *chk ) || *chk == 0 )
  {
    minutes = tmp;
  }
  else
  {
    // input was not a proper floating point value
  }
}

This has the benefit of not leaving crap in the input stream.

Upvotes: 2

Related Questions