ciouser-input

Storing user input into an array and terminating at stopping value without storing that value in C


I know how to write a while loop that reads user input, stores into an array then terminates at the stopping value. However, that stopping value is also stored and I'm not sure how to have it not do that.

Here is what I have:

printf("Enter temperatures or -100.0 to stop: "); // user input prompt
int i = 0;
while (1) {
  scanf("%f", &temp[i]);     // user input filling array
  if (temp[i] == -100.0) {   // stop loop if input = -100.0
    break;
  }
  i++;
}

Solution

  • Your immediate problem with your "stopvalue" being included in your temp[] array is because you perform the conversion with scanf() into &temp[i] which puts the value in your array regardless whether it is the stopvalue or not. To solve this problem, just declare a temporary variable and use that to hold the converted value. You can then check the temporary value against your stopvalue before adding it to your array.

    The manpage for scanf(), man 3 scanf warns:

    It is very difficult to use these functions correctly, and it is preferable to read entire lines with fgets(3) or getline(3) and parse them later with sscanf(3) or more specialized functions such as strtol(3).

    That is why time and again on StackOverflow you will find answers to user input questions in C recommending that scanf() isn't a wise choice for user input and you are better served by using fgets() to read the entire line and the parse any needed value from the line of input read. The reasons for this are many but come down to:

    Reading an entire line of user-input with fgets() avoids these problems, though you do still need to do proper validation by checking the fgets() return.

    The other problem you have is comparing floating point numbers for your stopvalue, e.g. if (temp[i] == -100.0). How close to -100.0 counts? Do you want to store -100.01? What about -100.04? What about -99.999? All of these values can compare equal to -100.0.1

    Generally choosing a Magic-Number as your stopvalue has problems. fgets() provides a simple solution to that issue as well. Rather than use a magic number, fgets() can check for no-input (e.g. '\n' only read) allowing the user to press Enter alone to end input. This allows all values to be considered valid temperatures without carving out a magic one.

    Note also, regardless of what method you choose to use, you cannot simply loop while(1) without any concern for your temp[] array bounds. Instead, as either a check within the loop, or as the loop test itself, you need to only take input while you have space in your array to hold it. For example if you have a 100 element array, you would want to do something like while (i < 100) { ... } to ensure you don't try and add more values than you have elements for. (of course, use a proper #define to provide the constant)

    Using scanf()

    Even with the shortcomings, if you check each needed condition and take care of extracting extraneous characters left in stdin, you can make scanf() work for input. It's not recommended, but you can do something like:

    #include <stdio.h>
    #include <math.h>
    
    #define NTEMPS    100   /* if you need a constant, #define one (or more) */
    #define STOPV    -100
    #define ELIMIT    1.e-4
    
    /* simple function to empty characters left in input stream */
    void empty_stdin (void)
    {
      int c = getchar();
      
      while (c != '\n' && c != EOF) {
        c = getchar();
      }
    }
    
    int main (void) {
      
      float temp[NTEMPS] = { 0 };     /* array to hold temps */
      int i = 0;                      /* counter variable */
      
      /* loop only while space remains in array */
      while (i < NTEMPS) {
        float tmp = 0;                /* temporary value for conversion */
        
        printf ("\nEnter temperatures or %d to stop: ", STOPV);
        fflush (stdout);              /* avoid line-buffering issues */
        
        if (scanf("%f", &tmp) != 1) { /* conversion to float failed */
          
          if (feof (stdin)) {         /* check for manual EOF */
            puts ("  user canceled input.");
            return 0;
          }
          else {  /* otherwise empty characters that remain in stdin unread */
            fputs ("  error: invalid floating-point input.\n", stderr);
            empty_stdin();
          }
          continue;                   /* get next input */
        }
        
        /* check for magic-number exit condition */
        if (fabs(tmp - STOPV) < ELIMIT) {
          empty_stdin();
          break;
        }
        
        temp[i] = tmp;                /* store converted value in array */
        i++;                          /* increment counter */
      }
      
      /* show output */
      puts ("\nRecorded Temps:\n");
      for (int j = 0; j < i; j++) {
        printf ("temp[%2d] : %.2f\n", j, temp[j]);
      }
    }
    

    note: the use of a range around -100 (within 1.e-4) that is considered your magic stopvalue.

    Using fgets()

    Not only is using fgets() recommended for user-input, it's actually easier to do that going through all the various checks and input buffer management you need to do with scanf(). Instead of using scanf() directly for input, all you are doing is declaring a character array to hold the user-input and then passing the array to sscanf() instead of doing input directly with scanf(). It makes things much easier (and shorter), e.g.

    #include <stdio.h>
    #include <string.h>
    
    #define MAXC    1024    /* if you need a constant, #define one (or more) */
    #define NTEMPS   100
    
    int main (void) {
      
      char buf[MAXC] = "";            /* array to hold line of user-input */
      int i = 0;                      /* counter variable */
      float temp[NTEMPS] = { 0 };     /* array to hold temps */
      
      /* loop continually reading input */
      while (i < NTEMPS) {
        float tmp;                    /* temporary value for conversion */
        
        /* prompt and flush output (no trailing '\n' in prompt) */
        fputs ("\nEnter temperatures (empty-input to stop) : ", stdout);
        fflush (stdout);
        
        /* read input, check for manual EOF */
        if (fgets (buf, MAXC, stdin) == NULL) {
          puts ("  user canceled input.");
          return 0;
        }
        
        if (buf[0] == '\n') {         /* if Enter pressed with no input */
          break;                      /* done! */
        }
        
        /* validate conversion to float */
        if (sscanf (buf, "%f", &tmp) == 1) {
          temp[i] = tmp;              /* add value to temperature array */
          i++;                        /* increment counter */
        }
        else {  /* otherwise conversion failed, show invalid input */
          buf[strcspn (buf, "\n")] = 0;   /* trim '\n' from string */
          fprintf (stderr, "  error: invalid floating-point input '%s'.\n",
                    buf);
        }
      }
      
      /* show output */
      puts ("\nRecorded Temps:\n");
      for (int j = 0; j < i; j++) {
        printf ("temp[%2d] : %.2f\n", j, temp[j]);
      }
    }
    

    Example use/Output

    $ ./read-float-arr-fgets
    
    Enter temperatures (empty-input to stop) : 10.8
    
    Enter temperatures (empty-input to stop) : -20.9
    
    Enter temperatures (empty-input to stop) : 59.1
    
    Enter temperatures (empty-input to stop) : 37.4
    
    Enter temperatures (empty-input to stop) : bananas and barbeque sauce
      error: invalid floating-point input 'bananas and barbeque sauce'.
    
    Enter temperatures (empty-input to stop) : -100.2
    
    Enter temperatures (empty-input to stop) : -99.99
    
    Enter temperatures (empty-input to stop) :
    
    Recorded Temps:
    
    temp[ 0] : 10.80
    temp[ 1] : -20.90
    temp[ 2] : 59.10
    temp[ 3] : 37.40
    temp[ 4] : -100.20
    temp[ 5] : -99.99
    

    Try entering "bananas and barbeque sauce" in your program and see what happens (hint: keep ctrl + c handy)

    For the scanf() implementation above, the only difference would be the stopvalue line as something like:

    Enter temperatures or -100 to stop: -100.0001
    

    Take the time to go through each example and lookup each function used and understand why it was necessary to do to create a reasonably robust input routine. If you have further questions, just drop a comment below and I'm happy to help further.

    Footnotes:

    1. Is floating point math broken? and Why Are Floating Point Numbers Inaccurate? and Floating point comparison a != 0.7