0

txt file:

44.56 john doe  
100.21 jane doe

How to calculate sum of numbers? This function give 0

double get_sum(FILE *out)
{
    double sum = 0;
    double value;
    char name;
    char surname;

    while(fscanf(out, "%lf %s %s", &value, name, surname) != EOF)
    {
        sum += value;
    }
    return sum;
}
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
  • 3
    If `fscanf` returned 0, then you are adding an uninitialized value. – Neil Jan 21 '22 at 21:07
  • Side note: Why are you naming the stream for the input file `out`? This name implies that it is an output file, not an input file. – Andreas Wenzel Jan 21 '22 at 21:13
  • 1
    Not your main problem, but: in your code as written, you really wanted `while(fscanf(out, "%lf %s %s", ...) == 3`, not the comparison against EOF that you had. – Steve Summit Jan 21 '22 at 21:25

2 Answers2

2

In the line

while(fscanf(out, "%lf %s %s", &value, name, surname) != EOF)

it is not good to compare the return value of the function fscanf with EOF. For example, if the function is only able to match one argument, then it will return 1, but your program will still behave as if all 3 arguments had been matched, and your program will attempt to process non-existant data. Therefore, you should write the following instead:

while( fscanf(out, "%lf %s %s", &value, name, surname) == 3 )

However, that is probably not the reason for the immediate problem that you are having. That problem is probably due to the fact that the %s conversion format specifier requires a pointer to write to. However, you are instead passing the value of (not a pointer to) a single char.

Your compiler should have warned you about this, assuming that you have warnings enabled. See this question for further information: Why should I always enable compiler warnings?

In order to solve this problem, you should change the lines

char name;
char surname;

to:

char name[50];
char surname[50];

Also, you should probably limit the number of characters written to these strings, in order to prevent a buffer overflow, like this:

while( fscanf(out, "%lf %49s %49s", &value, name, surname) == 3 )

For line-based input, I generally recommend that you read one line at a time with the function fgets. You can then use the function sscanf on every line, in order to parse it.

If you use instead fscanf as you do now, and if it does not match exactly 3 fields in one line, then the parser will go out of sync with the lines, and it won't be able to resync itself at the start of a new line, which will probably mean that your program will misbehave for the rest of the file, instead of only misbehaving for a single line.

Here is a program which uses fgets and sscanf instead:

#include <stdio.h>

double get_sum( FILE *fp )
{
    char line[200];
    double sum = 0;

    while ( fgets( line, sizeof line, fp ) != NULL )
    {
        double value;
        char name[50];
        char surname[50];

        if ( sscanf( line, "%lf %49s %49s", &value, name, surname ) == 3 )
        {
            sum += value;
        }
        else
        {
            printf( "WARNING: skipping line due to parse failure!\n" );
        }
    }

    return sum;
}

int main( void )
{
    //calling this function would also work for an opened file, but
    //for simplicity, I am only passing it "stdin"
    double sum = get_sum( stdin );

    printf( "The sum is: %lf\n", sum );
}

With the input

44.56 john doe
100.21 jane doe

from the question, this program has the following output:

The sum is: 144.770000

If you now instead supply the program with input that contains one line of invalid input

44.56 john doe
invalid_input_line
100.21 jane doe

it will only fail with parsing that invalid line, but will still process the other lines properly:

WARNING: skipping line due to parse failure!
The sum is: 144.770000

As already explained, your program is able to recover from this error because it is reading one line at a time using fgets, and it is using sscanf instead of fscanf. Otherwise, recovering from such an error would be much more complicated.

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

Just tell fscanf to ignore the name and surname slots like this:

double get_sum(FILE *out) {
    double sum = 0;
    double value;

    while (fscanf(out, "%lf%*s%*s", &value) != EOF) {
        sum += value;
    }
    return sum;
}

The trouble is that you where passing pointers to char instead of a pointer to a char array that can hold the content. Since you didn't it overflowed and caused undefined behavior.

But if you really wanted to also read in the names then try:

double get_sum(FILE *out) {
    double sum = 0;
    double value;
    char name[5];
    char surname[4];

    while (fscanf(out, "%lf%s%s", &value, name, surname) != EOF) {
        sum += value;
    }
    return sum;
}

Here the buffers are just long enough to accommodate the example text files data. In real life (if you needed it) you would have the big enough to handle the longest name. The important difference to your original code is that name and surname are pointers that the fscanf function expects.

But given that names sometimes are unpredictable in length, after reading the value on the line, just read the remaining line into a buffer and just ignore it.

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

double get_sum(FILE *in) {
    double sum = 0;
    double value;
    char *remainingLine = NULL;
    size_t bufLen = 0;

    while (fscanf(in, "%lf", &value) == 1) {
        sum += value;
        // ignore remaining character in line
        getline(&remainingLine, &bufLen, in);
    }
    free(remainingLine);
    return sum;
}

int main(void) {
    FILE *f = fopen("text.txt", "r");
    assert(f);
    double s = get_sum(f);
    fclose(f);
    printf("Sum is %f", s);
}

with text.txt file containing

1.03 First Surname
2.2Here is another long "name" (sum should be 10.63)
3.4 first middle last (next line is lonely and last)
4

Running the this last program should produce simething along the lines of

Sum is 10.630000
Bo R
  • 2,334
  • 1
  • 9
  • 17
  • I used the first code but still get 0.000 – user17997056 Jan 21 '22 at 21:14
  • I used `int main(void) { FILE *f = fopen("text.txt", "r"); assert(f); double s = get_sum(f); fclose(f); printf("Sum is %f", s); }` to test it. How did you test it? – Bo R Jan 21 '22 at 21:18
  • @BoR If you had typed two extra keystrokes to make it `char name[50];` and `char surname[40];`, you could have saved about 170 characters worth of explanation. :-) – Steve Summit Jan 21 '22 at 21:23
  • Same except assert(f). – user17997056 Jan 21 '22 at 21:31
  • @user17997056, without the check for `f`, how do you know the file is opened. Also remember, things start to go wrong if you data does not match the "specification". E.g., if one row containes `12.34 first middle surname` the the program falls apart due to three names being on on line. – Bo R Jan 21 '22 at 21:36
  • how do i implement assert(f)? – user17997056 Jan 21 '22 at 21:40
  • `#include ` – Bo R Jan 21 '22 at 21:40
  • still 0 can you paste your whole you used for testing – user17997056 Jan 21 '22 at 21:46
  • ISO C does not have a function [`getline`](https://man7.org/linux/man-pages/man3/getline.3p.html). That is a platform-specific extension (in this case POSIX). Please note that the question did not specify that it applies to any specific platform. You may want to consider rewriting that piece of code in a platform-independant way. – Andreas Wenzel Jan 21 '22 at 22:12
  • @BoR: An easy way to rewrite it may be `for ( int c; (c=getc(out)) != EOF && c != '\n'; );` However, even if that solution is compact, it is a bit ugly. A `do`...`while` loop may be a cleaner solution, such as `int c; do { c = getc( out ); } while ( c != EOF && c != '\n' );` – Andreas Wenzel Jan 21 '22 at 22:17
  • I set my mode to r instead of r+. It works – user17997056 Jan 21 '22 at 22:35