Home > database >  function only returning zero or very large numbers
function only returning zero or very large numbers

Time:10-06

Trying to write a program that reads input from a file and uses that to calculate a water bill using a function. Whenever I run the program and print the results of the function, it either prints 0, -0, or a very large number. I know the formulas I used are correct because I put them in a calculator and it worked fine, so the I think the problem is in my function or my function call.

first few lines of the file:

g 5000

B 1250

M 50

r 120

this is the output when I run my program:

Type    Water usage     Cost including tax

g       5000            0.000000

B       1250            -0.000000

Wrong user type.

M       50              0.000000

r       120             377145544395 (this number is much larger, I didn't want to copy the entire thing)

and my code:

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

    double water_billcalculation(char user_type, double water_use);


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

    char user_type;
    double water_use;

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

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



    return(0);
}

    double water_billcalculation(char user_type, double water_use) {
    
    double tc = 0, x;

    if (user_type == 'G' || user_type == 'g') {
        x = water_use * .035;
        tc = x   (x * .087)   3.75;

        
    }
    else if (user_type == 'B' || user_type == 'b') {
        x = water_use * .0553;
        tc = x   (x * .087)   17.25;
        
        
    }
    else if (user_type == 'R' || user_type == 'r') {
        if (water_use <= 400) {
            x = water_use * .04;
            tc = x   (x * .087)   13.5;
            
        }
        else if (water_use > 400 && water_use <= 700) {
            x = water_use * .062;
            tc = x   (x * .087)   13.5;
            
        }
        else {
            x = water_use * .12;
            tc = x   (x * .087)   13.5;
        
        }

    }
    else { 
        printf("Wrong user type.\n");
    }
    return(tc);
}

CodePudding user response:

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

Your format strings are messed up. The %d format specifier corresponds to a variable of type int (i.e. it needs an int for printf, and a pointer to int for scanf). But the variable water_use is of type double. This mismatch causes undefined behavior, which can include incorrect values. The type double should use %lf. (Other variants are possible, e.g. %lg, but I will not discuss them here. For printf the l is optional but for scanf it is mandatory, so we might as well use it consistently.).

Also, fscanf only returns EOF if an error occurred, or the end of the file was reached. If the file is mis-formatted and only one (or zero) of the input items are successfully matched, then fscanf may return 0 or 1 (number of items matched), in which case your program would go ahead with stale or uninitialized data in user_type and water_use. So to be safe, you should only proceed if fscanf returns 2.

As M.M. points out, \n\n in the fscanf format string can be replaced by a single \n or any other whitespace, but perhaps as it is, it helps document the file format that's expected. So I won't change that.

So change this to

    while (fscanf(water, "%c %lf\n\n", &user_type, &water_use) == 2) {
        printf("%c\t%lf\t\t%lf\n", user_type, water_use, water_billcalculation(user_type, water_use));
    }

A good compiler will warn you about the format string problem. gcc -Wall says:

water-orig.c: In function ‘main’:
water-orig.c:18:31: warning: format ‘%d’ expects argument of type ‘int *’, but argument 4 has type ‘double *’ [-Wformat=]
   18 |     while (fscanf(water, "%c %d\n\n", &user_type, &water_use) != EOF) {
      |                              ~^                   ~~~~~~~~~~
      |                               |                   |
      |                               int *               double *
      |                              %le
water-orig.c:19:22: warning: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘double’ [-Wformat=]
   19 |         printf("%c\t%d\t\t%lf\n", user_type, water_use, water_billcalculation(user_type, water_use));
      |                     ~^                       ~~~~~~~~~
      |                      |                       |
      |                      int                     double
      |                     %f

which would have explained the problem immediately and saved you an hour of writing this post and waiting for an answer.

CodePudding user response:

Your format string is messed up, with the wrong type and spurious spaces, and you're failing to check for invalid input. You should have:

while (fscanf(water, " %c%lf", &user_type, &water_use) == 2) {
  • to read a double you want %lf not %d (your compiler should be giving you a warning about this; if not you want to enable warnings)
  • you should compare the result of the scanf to the number of things you expect to be correctly parsed. This will deal properly with (by exiting the loop) reaching the end of file or having malformed data in the file
  • you should not have spurious whitespace in the format. You want a leading space to ignore any whitespace after the previous entry (any blank lines, for example), and you don't particularly want to deal with whitespace after an entry until you need the next entry.
  • Related