0

I am just a beginner to C language. I am trying to write a programme that take marks of four subjects. But in case if user provides a wrong input such as alphabets then it should print "Please Enter Number" and should again ask the marks of same subject.

Here is my code..

// Programme to add marks of four subject and to calculate percentage.
#include <stdio.h>
#include<ctype.h>
int main()
{
    float sub_marks, total_marks, temp, check = 0;

    printf("\nPragramme to add marks of four subject and to calculate percentage.\n\n");
    for (int i = 1; i <= 4; i++)         //Running loop four times to enter marks of four subjects.
    {

        printf("Enter Marks of Subject %d: ", i);
        scanf("%f", &sub_marks);

        if (sub_marks > 100)        //condition for what to do if marks are greater then 100
        {
            printf("\n**MARKS CONNOT BE GREATER THEN 100**\n\n");
            i--;
        }

        else
        {
            temp = temp + sub_marks;    //addind marks to get total marks
        }
    }

    printf("\n\nTotal Marks: 400");
    printf("\n\nObtained marks: %.2f", temp);
    printf("\n\nPercentage: %.2f%%\n\n", temp / 4);
    return 0;
}

I did try a lot but ended up with output..

Pragrame to add marks of fout subject and to calculate percentage.

Enter Marks of Subject 1: 65
Enter Marks of Subject 2: y

**PLEASE ENTER NUMBER.**

Enter Marks of Subject 3: 
**PLEASE ENTER NUMBER.**

Enter Marks of Subject 4: 
**PLEASE ENTER NUMBER.**

After entering alphabet it dosen't let the user to input for the rest of the loops. Instead it should ask "Enter Marks of Subject 1:"

I achived above output by placing below code after else.

 while (sub_marks >= 0)
        {

            remainder = sub_marks % 10;
            if (!isdigit(remainder))
            {
                printf("Please enter Number");
                break;
            }
        }
  • 2
    By getting a string input with `fgets` and applying `sscanf` to the string, it makes it far easier to recover from bad input. With `scanf` the bad input remains in the input buffer, but with `fgets` it is already in a string, which you can request again. – Weather Vane Mar 28 '22 at 17:21
  • 1
    Only increment `i` when you know you have a valid numeric input. – jarmod Mar 28 '22 at 17:24
  • When `scanf()` can't read a number when ask it to, it comes back with error leaving `char` input in `input-buffer`(sdtin). Only way to clean that invalid-input in input-buffer is to read it. So, employ `fgets()` & parse input. https://stackoverflow.com/questions/71285981/what-happens-when-you-use-scanf-for-a-decimal-but-enter-in-a-letter/71286032 – जलजनक Mar 28 '22 at 17:27
  • Adding to the previous suggestions, you should check also that `sscanf` consumed all of the input, in order to distinguish between `37` and `37hello`. – Costantino Grana Mar 28 '22 at 17:29
  • If the programme can be run in batch mode, very often just quitting with `EXIT_FAILURE` when unexpected input occurs is the most user-friendly thing to do. – Neil Mar 28 '22 at 22:19

2 Answers2

1

Unfortunately, the C standard library does not offer any easy way to read a floating-point number from the user, and to automatically keep asking the user until the input is valid. However, you can write such a function yourself, using the functions fgets and strtof.

In the code snippet below, I wrote a variadic function

float get_float_from_user( const char *prompt, ... )

which you can call like printf to print a string. It will repeatedly prompt the user for input using this string, until the input is valid. Once the input is valid, the function will return the user input converted to a float. You can call the function for example like this:

sub_marks = get_float_from_user( "Enter Marks of Subject %d: ", i );

If you replace the call to scanf with the line above, then your program should work as desired, after additionally fixing the following bug:

You must initialize temp to 0. The line

float sub_marks, total_marks, temp, check = 0;

will not initialize temp to 0. It will only initialize check to 0. If you want to initialize all 4 variables to 0, then you should instead write the following:

float sub_marks = 0, total_marks = 0, temp = 0, check = 0;

However, it would probably be better to change the name of temp to sum, as that describes the purpose of the variable better. Also you are not using the variables total_marks and check at all, so you can remove them. Therefore, you may want to change that line to the following:

float sub_marks, sum = 0;

Note that I am deliberately not initializing sub_marks, as that is not necessary (initializing sum is necessary, though).

However, since you are not using sub_marks outside the loop, it would probably be better to declare it inside the loop, in order to limit its scope.

Also, changing the loop counter i inside the loop is considered bad programming practice. A cleaner solution would be to create an additional loop inside the loop, so that the inner loop will only stop when the input is in the desired range.

Here is the code which does everything mentioned above:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>

float get_float_from_user( const char *prompt, ... )
{
    for (;;) //loop forever until user enters a valid number
    {
        char buffer[1024], *p;
        float f;
        va_list vl;

        //prompt user for input
        va_start( vl, prompt );
        vprintf( prompt, vl );
        va_end( vl );

        //get one line of input from input stream
        if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
        {
            fprintf( stderr, "unrecoverable error reading from input\n" );
            exit( EXIT_FAILURE );
        }

        //make sure that entire line was read in (i.e. that
        //the buffer was not too small)
        if ( strchr( buffer, '\n' ) == NULL && !feof( stdin ) )
        {
            int c;

            printf( "line input was too long!\n" );

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

                if ( c == EOF )
                {
                    fprintf( stderr, "unrecoverable error reading from input\n" );
                    exit( EXIT_FAILURE );
                }

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

            continue;
        }

        //attempt to convert string to number
        errno = 0;
        f = strtof( buffer, &p );
        if ( p == buffer )
        {
            printf( "error converting string to number\n" );
            continue;
        }

        //make sure that no range error occurred
        if ( errno == ERANGE )
        {
            printf( "number out of range error\n" );
            continue;
        }

        //make sure that remainder of line contains only whitespace,
        //so that input such as "6sdfh4q" gets rejected
        for ( ; *p != '\0'; p++ )
        {
            if ( !isspace( (unsigned char)*p ) )
            {
                printf( "unexpected input encountered!\n" );

                //cannot use `continue` here, because that would go to
                //the next iteration of the innermost loop, but we
                //want to go to the next iteration of the outer loop
                goto continue_outer_loop;
            }
        }

        return f;

    continue_outer_loop:
        continue;
    }
}

int main()
{
    float sum = 0;

    printf( "\nProgram to add marks of four subjects and to calculate average.\n\n" );

    for ( int i = 1; i <= 4; i++ )
    {
        float sub_marks;

        //loop forever until input is in the desired range
        for (;;)
        {
            sub_marks = get_float_from_user( "Enter marks of subject %d: ", i );

            if ( sub_marks < 0.0 )
            {
                printf( "Marks cannot be negative!\n" );
                continue;
            }

            if ( sub_marks > 100.0 )
            {
                printf( "Marks cannot be greater than 100!\n" );
                continue;
            }

            //input is in acceptable range, so break out of infinite loop
            break;
        }

        sum += sub_marks;
    }

    printf( "\n" );
    printf( "Total marks: 400\n" );
    printf( "Obtained marks: %.2f\n", sum);
    printf( "Average: %.2f%%\n", sum / 4.0 );
    printf( "\n" );

    return 0;
}

The program above has the following behavior:

Program to add marks of four subjects and to calculate average.

Enter marks of subject 1: This is a test.
error converting string to number
Enter marks of subject 1: 70
Enter marks of subject 2: 80abc
unexpected input encountered!
Enter marks of subject 2: 80
Enter marks of subject 3: 110
Marks cannot be greater than 100!
Enter marks of subject 3: 90.7
Enter marks of subject 4: abc85
error converting string to number
Enter marks of subject 4: 85

Total marks: 400
Obtained marks: 325.70
Average: 81.43%

The function get_float_from_user is a slight modification of my function get_int_from_user from the second code snippet of this answer of mine to another question.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
0

I've tried to put everything together as much as possible, but it's hard to read input correctly, managing everything that users can throw at you. This version is not perfect!

EDIT: As Andreas Wenzel pointed out: This solution has the following issues:

  1. If the user enters a line that is so long that it doesn't fit into the input buffer, then your program won't handle the input properly. Ideally, you should check for the newline character in the input buffer.
  2. The function sscanf has undefined behavior if the input is out of range of a float. This cannot be prevented when using the function sscanf. Therefore, it is generally not a good idea to use that function for input validation. The function strtof is better, as the behavior is well-defined for an out of range error.

The main thing is that you were computing the "average", not the "percentage".

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

int main(void)
{
    printf("Program to add marks of four subjects and to calculate the average.\n\n");
    float total_marks = 0;
    for (int i = 1; i <= 4; i++) { // Running loop four times to enter marks of four subjects.
        bool valid = false;
        while (!valid) {
            printf("Enter mark of subject %d: ", i);
            char line[256];
            if (fgets(line, 256, stdin) == NULL) {
                printf("\n**INPUT ERROR OR EOF**\n\n");
                exit(EXIT_FAILURE);
            }
            
            float mark;
            int n;
            if (sscanf(line, "%f %n", &mark, &n) != 1) {
                printf("\n**ENTER A FLOATING POINT VALUE**\n\n");
            }
            else if (line[n] != 0) {
                printf("\n**DON'T ADD ANYTHING AFTER THE NUMBER**\n\n");
            }
            else if (mark < 0 || mark > 100) {
                printf("\n**MARKS MUST BE BETWEEN 0 AND 100**\n\n");
            }
            else {
                valid = true;
                total_marks += mark; // add marks to get total
            }
        }
    }

    printf("Total Marks: %.2f\n", total_marks);
    printf("Average: %.2f\n\n", total_marks / 4);
    
    return EXIT_SUCCESS;
}
Costantino Grana
  • 3,132
  • 1
  • 15
  • 35
  • This solution has the following issues: 1. If the user enters a line that is so long that it doesn't fit into the input buffer, then your program won't handle the input properly. Ideally, you should check for the newline character in the input buffer. 2. The function `sscanf` has undefined behavior if the input is out of range of a `float`. This cannot be prevented when using the function `sscanf`. Therefore, it is generally not a good idea to use that function for input validation. The function `strtof` is better, as the behavior is well-defined for an out of range error. – Andreas Wenzel Mar 28 '22 at 22:45
  • @AndreasWenzel You perfectly summarized the "This version in not perfect!" Meaning. I was too lazy to spell it out completely. Answer edited to include the flaws. – Costantino Grana Mar 29 '22 at 08:46