0

I have a string file_string that uses the function fgets to make a string from a text file. This file could contain up to 10,000 floating point values, though it also has to handle bad values (such as 12.e, or 4e.3) by setting those values to -1000. I want to loop through the list and do this, but I am really struggling to find a method that works.

Currently, I tried to use fscanf while knowing how many values, but that doesn't seem to work either (wrong # of values getting computed, not recording data properly).

Here is the code snippet:

char file_string[50000];
fgets(file_string, 50000, fp);
float data[10000];
int count = 0;
int i;
int space=1;
i=0;
while(i<=file_string[i]){
    if(file_string[i]==' '){
        space++;
    }
    i++;
}
printf("File: %s\n", file_string);
printf("Spaces: %d\n", space);
while (count < space) {
    if (data[count] = fscanf(fp, "%f")) {
        printf("Data added: %f\n", data[count]);
    }
    else {
        data[count] = -1000;
        printf("Data added: %f\n", data[count]);
    }
    count++;
}

Any ideas? I also tried a sscanf implementation, but that did not seem to work either.

Thanks!

smallvt
  • 63
  • 6
  • 1
    `strtod()` was custom made for this purpose. There are many examples of `strtol()` and `strtod()` on this site. See also [strtod(3) — Linux manual page](https://man7.org/linux/man-pages/man3/strtod.3.html) – David C. Rankin Mar 05 '22 at 03:55
  • @DavidC.Rankin I will check that out- thanks. New to C, has been challenging – smallvt Mar 05 '22 at 03:56
  • 1
    You will fill your string with a number of floats using `fgets()`. Then you will use `strtod()` with the string as `nptr` and a second pointer to use as `endptr` (see man page). A call to `strtod()` will begin conversion at `nptr` converting the ASCII floating-point value to `double` and it will update `endptr` to the next character after the last used in conversion. So you just loop calling `strtod()` (validating the conversion) and then update `nptr = endptr;` to set up for the next call. That way you work through the string converting all floats, no matter how many you have. – David C. Rankin Mar 05 '22 at 03:59
  • 1
    Here is an uncommonly long answer showing the use of `strtol()` (which is essentially the same as `strtod()` but also requires the number base). The final example reads with `fgets()` and converts as many ASCII integer values as are found to `int`. You are doing the same thing with `float` (there is a specific `strtof()` function if your type is `float`). They all work the same way. [strtol() example](https://stackoverflow.com/a/53800654/3422102) – David C. Rankin Mar 05 '22 at 04:07
  • What is this supposed to do? `while(i<=file_string[i])`. As that array holds `char` elemets, you can never get past element `255` of your `50000` byte array. Assuming you read pain text, you will stop even earlier. Is that supposed to be some weird way to detect the terminating `0` byte? – Gerhardh Mar 05 '22 at 09:08

1 Answers1

1

While using strtod() (for double) or strtof() (for float) is the correct approach for the conversion, and can be used to work through all values in each line read, there actually is a simpler way to approach this problem. Since the sets of ASCII characters (tokens) to be converted to floating-point values are all separated by one or more spaces, you can simply use strtok() to split the line read into file_string into tokens and then convert each token with strtod() (or strtof())

What that does is simplify error handling and you having to increment the pointer returned in endptr past any characters not used in a conversion. If you process the entire line with strtod(), then for your 12e or 4e.3 examples of bad values, it would be up to you to check if endptr pointed to something other than a space (one after the last char used in the conversion), and if that was the case, you would have to manually loop incrementing endptr checking for the next space.

When you tokenize the line with strtok() splitting all space separated words in file_string into tokens, then you only have to worry about validating the conversion and checking if endptr is pointing at something other than the '\0' (nul-terminating character) at the end of the token to know if the value was bad.

A quick example of doing it this way could be:

#include <stdio.h>
#include <stdlib.h>   /* for strtod()  */
#include <string.h>   /* for strtok()  */
#include <errno.h>    /* for errno     */
#include <ctype.h>    /* for isspace() */

#define DELIM " \t\n" /* delmiters to split line into tokens */

int main (void) {

  char file_string[] = "-123.45 1.2e7 boo 1.3e-7 12e 4e.3 1.1 2.22\n",
       *nptr = file_string,       /* nptr for strtod */
       *endptr = nptr;            /* endptr for strtod */
  size_t n = 0;
  
  nptr = strtok (file_string, DELIM);     /* get 1st token */
  
  while (nptr != NULL) {
    errno = 0;                            /* reset errno 0 */
    
    double d = strtod (nptr, &endptr);    /* call strtod() */
    
    printf ("\ntoken: '%s'\n", nptr);     /* output token (optional) */
    
    /* validate conversion */
    if (d == 0 && endptr == nptr) { /* no characters converted */
      fprintf (stderr, "error, no characters converted in '%s'.\n", nptr);
      d = -1000;        /* set bad value */
    }
    else if (errno) {   /* underflow or overflow in conversion */
      fprintf (stderr, "error: overflow or underflow converting '%s'.\n", 
                nptr);
      d = -1000;        /* set bad value */
    }
    else if (*endptr) { /* not all characters used in converison */
      fprintf (stderr, "error: malformed value '%s'.\n", nptr);
      d = -1000;        /* set bad value */
    }
    
    printf ("double[%2zu]: %g\n", n, d);  /* output double */
    n += 1;           /* increment n */
    
    nptr = strtok (NULL, DELIM);    /* get next token */
  }
}

(note: the string you pass to strtok(), e.g. file_string in your case cannot be a read-only String-Literal because strtok() modifies the original string. You can make a copy of the string if you need to preserve the original)

Example Use/Output

With file_string containing "-123.45 1.2e7 boo 1.3e-7 12e 4e.3 1.1 2.22\n", you would have:

$ ./bin/strtod_strtok_loop

token: '-123.45'
double[ 0]: -123.45

token: '1.2e7'
double[ 1]: 1.2e+07

token: 'boo'
error, no characters converted in 'boo'.
double[ 2]: -1000

token: '1.3e-7'
double[ 3]: 1.3e-07

token: '12e'
error: malformed value '12e'.
double[ 4]: -1000

token: '4e.3'
error: malformed value '4e.3'.
double[ 5]: -1000

token: '1.1'
double[ 6]: 1.1

token: '2.22'
double[ 7]: 2.22

Look things over and let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85