0

I'm trying to write a program that uses a function to calculate a water bill. It reads the information about water usage from a file, then calculates the water bill after tax.

This is the file:

g 5000
B 1250
M 50

This is what the output should be:

Type  Water usage  Cost including tax($)
g     5000              194.30
B        1250              93.89
Wrong user type

This is my output:

Type  Water usage  Cost including taxWrong user type.
g     5000          0.000000
B     1250          18.750750
Wrong user type.

I'm not sure if the problem lies in my formula or something else. There's obviously a problem with the if else statement in the function since it keeps printing "Wrong user type" where it shouldn't.

This is my code:

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

double water_billcalculation(char user_type, double water_use, double bft, double at);

int main(void) {
    FILE* water;
    water = fopen("water_usage.txt", "r");

    char user_type;
    int cf;
    double bft = 0, at = 0, water_use = 0;

    printf("Type  Water usage  Cost including tax");

    while (fscanf(water, "%c%d ", &user_type, &cf) != EOF) {
        //water_billcalculation(user_type, water_use, bft, at);
        printf("%c     %d          %lf\n", user_type, cf, water_billcalculation(user_type, water_use, bft, at));
    }

    return(0);
}

double water_billcalculation(char user_type, double water_use, double bft, double at) {
    if (user_type == 'G') { 
        bft = (water_use * 0.035) + 3.75;
        at = bft + (bft * .087);
    }
    else if (user_type == 'B') { 
        bft = (water_use * .0553) + 17.25;
        at = bft + (bft * .087);
    }
    else if (user_type == 'R') { 
        if (water_use <= 400) {
            bft = (water_use * .04) + 13.5;
            at = bft + (bft * .087);
        }
        else if (water_use > 400 && water_use <= 700) {
            bft = (water_use * .062) + 13.5;
            at = bft + (bft * .087);
            
        }
        else {
            bft = (water_use * .12) + 13.5;
            at = bft + (bft * .087);
        }
    }
    else { 
        printf("Wrong user type.\n");
    }
    return(at);
}
einpoklum
  • 118,144
  • 57
  • 340
  • 684
ahsokatano
  • 11
  • 4
  • 2
    Your if clause covers `G`, `B`, and `R`. Your input data is `g`, `B`, and `M`. Two of the 3 types are invalid. – William Pursell Oct 04 '21 at 22:12
  • `while (fscanf(water, "%c%d ", &user_type, &cf) != EOF)` ==> `while (fscanf(water, " %c%d", &user_type, &cf) == 2)` moves the space before `%c` and changes the test (edit as Barmar). – Weather Vane Oct 04 '21 at 22:14
  • In the scanf format string, put the space before `%c`, not at the end. – Barmar Oct 04 '21 at 22:14
  • Why do you pass `at` and `bft` as parameters to the function? It doesn't use the parameters, it always replaces them. They should be local variables in the function, not parameters. – Barmar Oct 04 '21 at 22:16
  • You are not passing the input value `cf` to the calculation function. You are passing `water_use` which is `0`. – Weather Vane Oct 04 '21 at 22:18
  • BTW, "Wrong user type" feels very much like an error message. Error messages belong on stderr. `fprintf(stderr, "Invalid user type: %c\n", user_type);` – William Pursell Oct 04 '21 at 22:25
  • If you use `fscanf` like that, you *should* call `ferror` after the loop to check for a potential error state that you need to warn the end user about. – Cheatah Oct 05 '21 at 00:41

2 Answers2

4

The problem

You're "reading" your file in a wrong way. Change this line:

    while (fscanf(water, "%c%d ", &user_type, &cf) != EOF) {

to this (notice the difference in the second argument of fsanf):

    while (fscanf(water, " %c%d", &user_type, &cf) == 2) {

Also your water_billcalculation is wrong: You're looking after the user_type G, B and R according to your code, but you're actually looking after g, B and M! So you'd need to change this part:

    if (user_type == 'G') { 
        bft = (water_use * 0.035) + 3.75;
        at = bft + (bft * .087);
    }
    else if (user_type == 'B') { 
        bft = (water_use * .0553) + 17.25;
        at = bft + (bft * .087);
    }
    else if (user_type == 'R') { 

to this:

    if (user_type == 'g') { 
        bft = (water_use * 0.035) + 3.75;
        at = bft + (bft * .087);
    }
    else if (user_type == 'B') { 
        bft = (water_use * .0553) + 17.25;
        at = bft + (bft * .087);
    } else if (user_type == 'M') { 

After this, you'll get the following output:

Type  Water usage  Cost including taxg     5000          4.076250
B     1250          18.750750
M     50          14.674500

I'm a little bit unsure, if this is your desired output, because according to your post which says, that you'd like to have this output:

Type  Water usage  Cost including tax($)

g     5000              194.30

B        1250              93.89
 
Wrong user type

makes me thinking that:

  • You actually don't want to "parse" the type M. If yes, remove the else if (user_type == 'M') condition in the water_billcalculation function.
  • The calculation isn't correct or I don't see another bug in my code (althought I even ran it with valgrind, which didn't complain about any invalid actions).

Code review

Some improvement ideas came up in my mind, when I read your code. So here are some suggestions. You can skip this, if you aren't interested in them.

Check the return value after calling fopen!

fopen returns you a FILE * but only IF everything worked fine! So please make sure to add a lookup part:

    FILE * water;
    water = fopen("water_usage.txt", "r");

    if (water == NULL) {
        // Thanks to @William Pursell for pointing out to use stderr for error messages, I forget that pretty often
        perror("Houston, we've got a problem: The file couldn't be opened :(\n");
        // or use the `exit()` function here (but you'd need to include stdlib.h)
        return 1;
    }

String formatting

I'd recommend to use \t instead of counting your spaces. This would let you produce a better output. Also don't forget to add a \n if you're printing something in a new context.

In the beginning when I've got this ouptut:

Type  Water usage  Cost including taxg     5000          4.076250
B     1250          18.750750
M     50          14.674500

I was a little bit irritated first, because I didn't saw the line with the g type.

Change your output to the following:

    printf("Type\tWater usage\tCost including tax\n");

    while (fscanf(water, "%c %d\n\n", &user_type, &cf) != EOF) {
        printf("%c\t%d\t\t%lf\n", user_type, cf, water_billcalculation(user_type, water_use, bft, at));
    }

This gives me the following output:

Type    Water usage     Cost including tax
g       5000            4.076250
B       1250            18.750750
M       50              0.000000

which is better to read (in my opinion).

(Optional) use switch-case

Your water_billicalculation could include a switch-case statement instead of nested if-else statements.

I'd have the written the function as follows:

double water_billcalculation(char user_type, double water_use, double bft, double at) {

    switch (user_type) {
        case 'g':
            bft = (water_use * 0.035) + 3.75;
            at = bft + (bft * .087);
            break;
        case 'B':
            bft = (water_use * .0553) + 17.25;
            at = bft + (bft * .087);
            break;
        case 'M':
            if (water_use <= 400) {
                bft = (water_use * .04) + 13.5;
                at = bft + (bft * .087);
            }
            else if (water_use > 400 && water_use <= 700) {
                bft = (water_use * .062) + 13.5;
                at = bft + (bft * .087);
                
            }
            else {
                bft = (water_use * .12) + 13.5;
                at = bft + (bft * .087);
            }
            break;
        defaut:
            fprintf(stderr, "Wrong user type: '%c'\n", user_type);
            break;
    }
    return at;
}

instead of this:

    if (user_type == 'g') { 
        bft = (water_use * 0.035) + 3.75;
        at = bft + (bft * .087);
    }
    else if (user_type == 'B') { 
        bft = (water_use * .0553) + 17.25;
        at = bft + (bft * .087);
    }
    else if (user_type == 'M') { 
       // and so on...

but that's probably very individual, so see this as an opinion please.

TornaxO7
  • 1,150
  • 9
  • 24
  • Thank you for pointing out my mistakes! I fixed your suggestions from 1 to 3 but I'm unsure what you mean with the fourth. Could you please explain it a little bit further? – TornaxO7 Oct 05 '21 at 18:59
  • Hm... I thought that we'd need the `\n` to make sure that it's parsing a line. I'll add your improvements. Thank you! Looks like that I need to write some C code again... – TornaxO7 Oct 06 '21 at 12:31
  • "need the \n to make sure that it's parsing a line" --> Yes, a `"\n"` does look for a `'\n'` (or any white-space) yet it does more - unfortunately. It directs scanning to continue looking for any white-space and does not return until a non-white-space is detected. – chux - Reinstate Monica Oct 06 '21 at 12:39
  • You might want to try `while (fscanf(water, "%c%d", &user_type, &cf) == 2) {`. I suspect then you will see why `" %c%d"` is better. – chux - Reinstate Monica Oct 06 '21 at 12:41
  • 1
    I just created a gist to test it out: https://gist.github.com/TornaxO7/b2a4227bf9c9472a8f0f80f307c84144 When removing the whitespace before `%c` the program ends after the second input. Good to know. Thank you! – TornaxO7 Oct 06 '21 at 19:54
  • "The file couldn't be found" is a very frustrating error message when `fopen` fails because of a permission error. Just do: `char *path="water_usage.txt"; if( (water = fopen(path, "r")) == NULL ){ perror(path); ...` – William Pursell Oct 06 '21 at 21:02
  • Good point! I forgot this function. – TornaxO7 Oct 07 '21 at 00:13
1

While @TornaxO7 answered your literal question, you would have been able to easily determine what the problem is by debugging your program:

How to debug a C program

Specifically,

  • By stepping your program or breaking after the scan, you would have noticed what values you're getting in user_type and in cf.
  • By stepping through the comparisons in water_billcalculation(), you would have noticed how comparisons you expected to succeed, fail - which would have led you to notice you're comparing 'g' with 'G'.

Additionally, or alternatively, adding some "debug-prints" or log-type printing to your program, at least while developing it, provides some of the same information even in regular runs without debugging.

einpoklum
  • 118,144
  • 57
  • 340
  • 684