Home > other >  C Program -- While loop with scanf function condition runs even if condition is not met. How to fix
C Program -- While loop with scanf function condition runs even if condition is not met. How to fix

Time:02-17

I am attempting to create a program where you input 2 numbers and then print out the first number to the power of the second number.

I tried using a while loop to say that if the numbers you input are two, then you keep repeating the program, otherwise if you input more than 2, you end the loop and print out that you input too many numbers.

However, the code still works if I input more than two, and I assume this is because the scanf function ignores anything other than the first two numbers I input.

How do I fix this program so that it works as I had intended?

#include <stdio.h>

#include <math.h>

int main(void)

{

    float x, exp;

    printf("Please enter a number followed by the power ");
    printf("you want to raise it to: ");
    

    while(scanf("%f%f", &x, &exp) == 2)
    {
        printf("%f\n", pow(x, exp));
        printf("Enter the next pair of numbers:\n");
    }
    
    printf("You entered too many numbers!\n");

    return 0;
}

CodePudding user response:

User input is tricky. Get input as a string, and loop on that. Just keep in mind that the user may enter each input one at a time. Either require it to be correct (user types two numbers followed by Enter) or take effort to handle multiple correct inputs (user types one number followed by Enter and then another number followed by Enter). Here we will require both inputs on the same line:

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

int main(void)
{
    float x, exp;

    printf("Please enter a number followed by the power ");
    printf("you want to raise it to: ");

    char s[1000];
    while (fgets(s, sizeof(s), stdin))
    {
        if (sscanf(s, "%f %f", &x, &exp) != 2)
        {
            puts("Invalid input, my dude.");
            break;  // Stop running if user supplies fewer than two valid inputs
        }
        else
        {
            printf("%f\n", pow(x, exp));
        }
        printf("Enter the next pair of numbers:\n");
    }

    return 0;
}

This requires the user to terminate the stream to quit, BTW, by pressing ^Z,Enter on Windows or ^D on Linux. You could easily add additional methods to terminate in the loop (for example, terminate if s is empty or sscanf returns 0), but this is not necessary.

EDIT: There are other issues too. For example, what if the user enters more than two inputs on a line. Should I detect that? Again, for programs like this, it is ok to assume that inputs will be valid unless your assignment specifically requires you to detect error conditions.

EDIT 2: If you wish to catch a more than two items entered error, you must make sure that sscanf() consumed the entire line. Fortunately there is an easy way to do that. Change line 15 to:

        int n;
        if ((sscanf(s, "%f %f %n", &x, &exp, &n) != 2) || (s[n] != '\0'))

What that does is skip all whitespace after the second float to either end of string or the next available item in the string and returns the index of that position in n.

After that we only need to verify that the end of string condition is what was found.

CodePudding user response:

If the user types more than two numbers this will not be an error. They will be stored in the input buffer and read in the next call of scanf.

Pay attention to that the user can type two numbers on the same line or in different lines.

So you need to split the input.

The first number will be read using scanf and the second number will be read using fgtes.

Here is a demonstration program.

#include <stdio.h>
#include <math.h>

int main(void) 
{
    printf("Please enter a number followed by the power ");
    printf("you want to raise it to: ");
    
    while ( 1 )
    {
        float x, exp;

        if ( scanf( "%f ", &x ) != 1 ) break;
        
        char s[20];
        
        if ( !fgets( s, sizeof( s ), stdin ) ) break;
        
        int n;
        
        if ( sscanf( s, "%f %n", &exp, &n ) != 1 || s[n] != '\0' ) break;

        printf("%f\n", pow(x, exp));
        printf("Enter the next pair of numbers: ");
    }
    
    puts( "You entered too many or too few numbers!" );    

    
    return 0;
}

Its output might look like

Please enter a number followed by the power you want to raise it to: 1 2
1.000000
Enter the next pair of numbers: 2
3
8.000000
Enter the next pair of numbers: 4
5 6
You entered too many or too few numbers!

CodePudding user response:

Simply put, your code will always continue. This is just because of how scanf works:

Scanf scans input from stdin. When scanf reaches the end of stdin and still hasn't scanned everything it expected to scan, it waits until the user sends a newline (presses enter). In other words, so long as you enter valid floats, your scanf will never return a value lower than the expected float count.

On the other end, once scanf is finished with scanning stdin, it immediately evaluates the variables and returns. This means that there is still some input left in stdin that has not yet been read. In fact, when scanf next runs, it will resume scanning exactly where it left off. Take this sample code:

int main()
{
    int x,y;
    int ct = scanf("%d%d",&x,&y);
    printf("%d (%d,%d)\n",ct,x,y);
    scanf("%d",&x);
    printf("%d\n",x);
}

If you compile and run this, try inputting three ints at once. The second scanf will immediately terminate because it is reading the third integer that was inputted.

If you are trying to get a specific number of inputs, I would suggest scanning the user's input as a string and then using sscanf (scanf for strings). You could also check for the number of spaces in the string, then, to determine if there are too many inputs. If you want to get a little tricky, you could continue to use scanf but then check whether bytes are remaining in stdin before you continue. Here is a good answer that will help if you want to keep using scanf as is, but checking whether stdin is empty.

There is another issue with your code though; what happens when a user inputs something other than a float? But that is a different question entirely (and one where my personal suggestion would be to analyze the entire scanned string).

CodePudding user response:

The problem with using scanf is that it treats all whitespace characters (e.g. spaces and newline characters) as equal. For example, scanf won't care whether the numbers you entered are on the same line or not.

If scanf is asked to read two numbers, but the user instead enters three numbers on the same line, then your first call to scanf will only read the first two numbers and leave the third number on the input stream, so that the next scanf call in the next loop iteration will read it as the next first number. This is not what you want.

Therefore, for line-based user input, it is probably better not to use scanf. Instead, it makes more sense to always read exactly one line per loop iteration. You can do this with the function fgets.

After using fgets to read a line of input, two of the other answers use sscanf to convert both numbers at once. However, you can also convert one number at a time using strtof:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include <stdbool.h>

int main(void)
{
    //we want to provide the user with a different prompt the first time,
    //so we must remember whether it is the first time
    bool first = true;

    //infinite loop
    while ( true )
    {
        float x, exp;
        char line[100];
        char *p, *q;

        //prompt user for input
        if ( first )
        {
            printf(
                "Please enter a number followed by the power "
                "you want to raise it to: "
            );

            //remember to use a different prompt next time
            first = false;
        }
        else
        {
            printf("Enter the next pair of numbers: ");
        }

        //attempt to read one line of input
        if ( fgets( line, sizeof line, stdin ) == NULL )
        {
            //break out of infinite loop
            break;
        }

        //attempt to find newline character
        p = strchr( line, '\n' );

        //make sure entire line was read in
        if ( p == NULL && !feof(stdin) )
        {
            //only accept missing newline character on end-of-file
            if ( !feof(stdin) )
            {
                int c;

                printf( "Line too long for input buffer!\n" );

                //discard remainder of line
                do
                {
                    c = getchar();

                } while ( c != EOF && c != '\n' );

                continue;
            }
        }
        else
        {
            //remove newline character by overwriting it with null character
            *p = '\0';
        }

        //start parsing at start of line
        p = line;

        //attempt to convert first number
        x = strtof( p, &q );

        //determine whether conversion of first number succeeded
        if ( p == q )
        {
            printf( "Conversion of first number failed!\n" );
            continue;
        }

        //continue parsing at end of first number
        p = q;

        //attempt to convert second number
        exp = strtof( p, &q );

        //determine whether conversion of second number succeeded
        if ( p == q )
        {
            printf( "Conversion of second number failed!\n" );
            continue;
        }

        //verify that remainder of line is either empty or only
        //consists of whitespace characters
        for ( p = q; *p != '\0'; p   )
        {
            if ( !isspace( (unsigned char)*p ) )
            {
                printf( "Unexpected character found after second number!\n" );

                //we cannot use the "continue" keyword here, because
                //we want to continue to the next iteration of the
                //outer loop, not the inner loop
                goto continue_outer_loop;
            }
        }

        printf( "Input accepted, the result is: %f\n", pow(x, exp) );

    continue_outer_loop:
        continue;
    }

    return 0;
}

This program has the following behavior:

Please enter a number followed by the power you want to raise it to: abc
Conversion of first number failed!
Enter the next pair of numbers: 10
Conversion of second number failed!
Enter the next pair of numbers: 10 abc
Conversion of second number failed!
Enter the next pair of numbers: 10 20
Input accepted, the result is: 100000000000000000000.000000
Enter the next pair of numbers: 10 20 30
Unexpected character found after second number!

As you can see, the program correctly rejects the input if it contains a third number.

  •  Tags:  
  • c
  • Related