Search code examples
cfiletextiofloating-point

Read a line from a .txt file and split it to float numbers


I have a .txt file that contains an unknown (each time) number of lines. In every line there are 4 floats separated by the space character.

My problem is that I have no clue how to separate each float from the others in each line and put it in the right place of the matrix.

This is the code I have done so far (it goes through every line for the first time so that I know the number of the lines to declare my matrix, but it only returns just the first of the four values):

#include <stdio.h>

FILE *fr;

main () {

    float time, xaxis, yaxis, zaxis;
    char line[40];
    int n = 0;

    fr = fopen ("walk-after-excel.txt", "r");

    while (fgets (line, 80, fr) != NULL) {
        n++;
    }
    rewind (fr);
    printf ("%u", n);
    float values[n][4];
    int i;
    for (i = 0; i < n; i++) {
        values[i][0] = 0;
        values[i][1] = 0;
        values[i][2] = 0;
        values[i][3] = 0;
    }
    while (fgets (line, 80, fr) != NULL) {
        sscanf (line, "%f", &time);
        printf ("%f\n", time);
    }
    for (i = 0; i < n; i++) {
        printf ("%f, %f, %f, %f \n", values[i][0], values[i][1], values[i][2],
                values[i][3]);
    }
    printf ("%u", n);
    fclose (fr);
}

Any help will be very appreciated.


Solution

  • You've got a few issues here:

    1. In modern C, you should always specify a return type for your functions, including main. This means you should use int main(void) { ... }.
    2. You specify a buffer size of 40 bytes, but you tell fgets that your buffer is 80 bytes, this is a huge no-no. You must never specify to functions that your buffer is bigger than it really is, or else you will encounter buffer overflows (which in the real world can become exploitable security holes). Because you are using an array type, you can simply pass sizeof line to fgets, and then it won't matter if you change the size of line, it will always pass the correct value to fgets.
    3. You are only scanning in the first variable of each line, that's why you're only seeing the first value of each line.
    4. You're declaring a VLA (variable length array). These are useful but in most (if not all) implementations, VLAs consume stack space, which is limited. If you try and declare a VLA that is too large, it will likely crash your application. The way around this is to use malloc or calloc. calloc is like malloc except that it automatically zeroes out the allocated memory for you.
    5. There is no need for your fr variable to be global.

    Since each line of the file represents a “record”, you could declare a structure, like this:

    struct record
    {
        float time;
        float xaxis;
        float yaxis;
        float zaxis;
    };
    

    Also, since there are an unknown number of records in the file, there are at least two ways to allocate enough memory to hold all records. One is to count all the lines in the file first, then allocate the memory based on the number of lines. This has the benefit that you will allocate exactly the right amount of memory that you need, but requires that you read the file twice. Another way is to allocate enough memory to hold a decent number of records and then “grow” this memory allocation if it turns out it wasn't big enough. This has the benefit that you only need to read the file once, but you may end up wasting memory.

    You're already using the “count number of records first, allocate later” approach, so we just need to fix up the aforementioned issues.

    #include<stdio.h>
    
    struct record
    {
        float time;
        float xaxis;
        float yaxis;
        float zaxis;
    };
    
    int main(void)
    {
        FILE *fr;
        char line[80];
        int n = 0;
    
        fr = fopen ("walk-after-excel.txt","r");
        if (fr == NULL)
        {
            printf("Couldn't open file!\n");
            return 1;
        }
    
        while(fgets(line, sizeof line, fr) != NULL)
        {
            n++;
        }
        rewind(fr);
        printf("%d",n); // use %d for int types
    
        struct record *records = calloc(n, sizeof(struct record));
    
        for (int i = 0; i < n; i++)
        {
            fgets(line, sizeof line, fr);
    
            sscanf(line, "%f %f %f %f", &records[i].time, &records[i].xaxis, &records[i].yaxis, &records[i].zaxis);
        }
    
        // records now contains all the data in the file!
    
        // to access the 5th line's xaxis value, use:
        printf("%f\n", records[4].xaxis);
        // (remember that arrays in C are 0-based)
    
        // when you are done, free the memory allocated by malloc/calloc with free()
    
        free(records);
    
        fclose(fr);
    }