Home > Software design >  Reading Numbers (integers and decimals) from file w/period as delimiter
Reading Numbers (integers and decimals) from file w/period as delimiter

Time:05-10

Currently, I wanted to fetch the file with delimiter .. However, the using fscanf unable to differentiate decimals and integers.

Wanting to extract first two integers (separated by .) and the final decimals into tmp[]

Tried fscanf(file, "%d.", &tmp[j]); with %d, %d. and %f, neither had worked.


Result with %d: tmp[3]: {1.61149323e-43, 0, 0.409999996}

Result with %d.: tmp[3]: {1.61149323e-43, 5.7453237e-44, 24.3600006}

Result with %f: tmp[3]: {115.410004, 24.3600006, 113.489998}

%d and %d. [0] and [1] got gibberish, only %d. [3] got to be seems read right.
%f seems reading good, but it fused in the first two numbers into one decimals.

Do I had to use fgets() or fgetc()? or I can just change fscanf() format specifier to get it right?


Expected Result: (in debugger)

tmp[0]: 115
tmp[1]: 41
tmp[2]: 24.36

(file line 2 as input)
tmp[0]: 113
tmp[1]: 49
tmp[2]: 44

Source:

#include <stdio.h>
#include <stdlib.h>
int main() {
    FILE *file = fopen("data.txt", "r");
    float tmp[3] = {};
        for(int j = 0; j < 2; j  ) {
            fscanf(file, "%d.", &tmp[j]);
        }
        fscanf(file, "%f", &tmp[2]);
} 

Data.txt

115.41.24.360
113.49.44
115.41.51.573

CodePudding user response:

The %d conversion specification is for reading a decimal integer, and expects an int * argument. Similarly, the %i, %o, %u, %x and %X conversion specifications are also for integers.

Using %f is not going to work; it will read the 'second integer' as the fraction part of the first floating-point value, and there's no way to stop it doing that (unless you can safely use ? because there'll always be at most 3 digits — which is a dubious proposition).

You'll need to use

int i1, i2;
if (fscanf(file, "%d.%d.%f", &i1, &i2, &tmp[2]) != 3)
{
    …handle error…
}
tmp[0] = i1;
tmp[1] = i2;

This reads the integer values into int variables, then assigns the results to the float array elements. Note that you should always check the return value from fscanf() and friends. If it isn't what you expect, there's a problem (as long as your expectations are correct, which they will be once you've read the specification for fscanf() often enough — a couple of times a day for the first couple of days, once a day for the next week, once a week for the next month, and about once a month for the next year).

CodePudding user response:

Whenever you have to handle multiple types as a single object, you should be thinking struct to provide storage. Here you have two integers and a float you want to store. A simple struct allowing that would be:

typedef struct {        /* struct to hold data from line */
  int n1, n2;
  float f;
} twointf;

The typedef is for convenience allowing you to refer to twointf as the type instead of repeating struct twointf each time. With a struct, you can now declare a simple array of twointf to handle as many as needed.

To read you data, reading an entire line with fgets() into into a sufficiently sized buffer (character array) and parsing with sscanf() is always preferred over a direct read with fscanf(). If there is an error in your data file, fgets() will read the entire line into the buffer and the error will only effect a single line of data. With fscanf() a Matching-Failure can occur, or your read will wrap to the next line with an omitted value corrupting your read from that point forward in the file.

To read with fgets() and separate with sscanf() you can do:

#define MAXC 1024       /* if you need a constant, #define one (or more) */
#define MAXS   32
...
    char buf[MAXC];                       /* read buffer for line */
    size_t ndx = 0;                       /* array of struct index */
    twointf arr[MAXS] = {{ .n1 = 0 }};    /* array of struct */
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    ...
    /* while array not full, read line */
    while (ndx < MAXS && fgets (buf, MAXC, fp)) {
      if (sscanf (buf, "%d.%d.%f",    /* separate into 2 int and float */
                  &arr[ndx].n1, &arr[ndx].n2, &arr[ndx].f) == 3) {
        ndx  ;    /* on success, increment index */
      }
    }

That's all there is to it. Read your data, convert with sscanf() validating the return and only incrementing your array index on successful conversion of all values.

Putting it altogether you could do:

#include <stdio.h>

#define MAXC 1024       /* if you need a constant, #define one (or more) */
#define MAXS   32

typedef struct {        /* struct to hold data from line */
  int n1, n2;
  float f;
} twointf;

int main (int argc, char **argv) {
    
    char buf[MAXC];                       /* read buffer for line */
    size_t ndx = 0;                       /* array of struct index */
    twointf arr[MAXS] = {{ .n1 = 0 }};    /* array of struct */
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }
    /* while array not full, read line */
    while (ndx < MAXS && fgets (buf, MAXC, fp)) {
      if (sscanf (buf, "%d.%d.%f",    /* separate into 2 int and float */
                  &arr[ndx].n1, &arr[ndx].n2, &arr[ndx].f) == 3) {
        ndx  ;    /* on success, increment index */
      }
    }
    
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);
    
    for (size_t i = 0; i < ndx; i  )  /* output results */
        printf ("\narr[%zu].n1 = %d\narr[%zu].n2 = %d\narr[%zu].f  = %.2f\n",
                i, arr[i].n1, i, arr[i].n2, i, arr[i].f);
}

Example Use/Output

With your sample data in dat/data_int_float.txt, you would have:

$ ./bin/twointf dat/data_int_float.txt

arr[0].n1 = 115
arr[0].n2 = 41
arr[0].f  = 24.36

arr[1].n1 = 113
arr[1].n2 = 49
arr[1].f  = 44.00

arr[2].n1 = 115
arr[2].n2 = 41
arr[2].f  = 51.57

(you can use the %g format specifier for floating-point to only output the decimal places if fractional data exists -- up to you)

  • Related