2
#include <stdio.h>
#include <ctype.h>

void task(const char *file_name)
{
    FILE *file = fopen("1.txt", "r");
    
    if (file == NULL){
        printf("Error opening file.\n");
        return;
    }
    
    int num;
    int min =2147483647 ;
    int min_count=0;
    
    while (fscanf(file, "%d", &num) !=isdigit(num)) {
        
        if (num < min) {
            min = num;
            min_count = 0;
        }
        if (num == min){
            min_count += 1;
        }
    }
    printf("Minimum:%d exists %d time(s) ",min, min_count);
    fclose(file);
}

int main()
{
    task("numbers.txt");
    return 0;
}

So I have a code which opens a file ,scans it and as a result I need to get number of minimal numbers in file Right now when it encounters not-numeric characters it crashes How do I modify my program to work despite encountering this characters?

8
  • 1
    Take the input as strings & do the conversion in your code. Commented Oct 3, 2024 at 17:53
  • 2
    isdigit(num) does not do what you want it to do: see pubs.opengroup.org/onlinepubs/9699919799/functions/isdigit.html; fscanf() does not return what you expect it to return either: see pubs.opengroup.org/onlinepubs/9699919799/functions/fscanf.html Commented Oct 3, 2024 at 17:53
  • What should happen if the file contains something which is not a number? E.g.: 1 2 3x4 5 What do you want to handle these cases? Option 1: if you fail to read a number, stop scanning (result 1 2 3). Option 2: ignore any non numeric character treating it as a space (in the previous case you expect to read 1,2,3,4,5). Option 3: ignore any non numeric character treating it as nothing (in the previous case you expect to read 1,2,34,5). Someone could come up with other options. What's yours? Commented Oct 3, 2024 at 18:02
  • Which operation crashes your app? fscanf(file, "%d", &num) or isdigit(num)? Commented Oct 3, 2024 at 18:10
  • Do you know, that isdigit(49) returns any value !=0, and isdigit(1) returns always 0 for not-a-digit. Commented Oct 3, 2024 at 18:11

3 Answers 3

1

When you call fscanf the return value indicates how many values were successfully read. You're looking for 1 to indicate successfully reading a (base 10) integer from the file in your case.

    while (fscanf(file, "%d", &num) == 1) {        
        if (num < min) {
            min = num;
            min_count = 0;
        }
        if (num == min){
            min_count += 1;
        }
    }
Sign up to request clarification or add additional context in comments.

Comments

1

Since you don't want to stop in case of non numeric data, here's how you can tackle the problem: check the return value of fscanf() and chose your next action based on that.

Notice the loop and a half pattern.

#include <stdio.h>
#include <limits.h>
#include <stdbool.h>

void task(const char *file_name)
{
    FILE *file = fopen(file_name, "r");

    if (file == NULL) {
        printf("Error opening file.\n");
        return;
    }

    int min = INT_MAX;
    int min_count = 0;
    while (true) {
        int num;
        int res = fscanf(file, "%d", &num);
        if (res == EOF) { 
            // In case of read failure we stop
            break;
        }
        if (res != 1) {
            // We couldn't read an int. fscanf stopped at the first character
            // responsible for the error. Let's skip it and try again.
            fgetc(file);
            continue;
        }
        // We successfully read an int: let's process it.
        if (num < min) {
            min = num;
            min_count = 0;
        }
        if (num == min) {
            min_count += 1;
        }
    }
    printf("Minimum: %d exists %d time(s)\n", min, min_count);
    fclose(file);
}

int main()
{
    task("numbers.txt");
    return 0;
}

Example of numbers.txt:

1 2 3 hello 4 5
test 3 2 1 -2 -3 
a -2-3 1 2 3

On another note, if you really don't like the loop and a half pattern, you could fake a more classical while like this:

    int res, num;
    while ((res = fscanf(file, "%d", &num)) != EOF) {
        if (res != 1) {
            // We couldn't read an int. fscanf stopped at the first character
            // responsible for the error. Let's skip it and try again.
            fgetc(file);
            continue;
        }
        // We successfully read an int: let's process it.
        ...

But the additional parentheses give me a negative feeling. Too easy to forget. Everything look too tightly packed. But it's just a personal opinion.

4 Comments

Your else looks like a guard. I might put it below the break case and add a continue.
@Chris Something like (after the break): if (res != 1) { fgetc(file); continue; } Followed by the main logic of the problem?
Precisely what I was thinking.
@Chris You're right. Edited.
0

How do I make my program not break when symbol is non-numeric?

while (fscanf(file, "%d", &num) !=isdigit(num)) { has problems.

  • The return value should be a check against 1.

  • If the text version of the number is out of int range, using %d is undefined behavior.

  • It remains unclear where code should continue reading when things fail.


When the return value is 0, no conversion, go back to the file offset where code was plus 1 and then try again.

If we assume any number is in the [INT_MIN ... INT_MAX] range, we could use:

    int retval;
    long location = 0;
    while ((retval = fscanf(file, "%d", &num)) != EOF && location != -1) {
      if (retval == 1) {
        // Perform your metric on `num`.
        if (num < min) {
            min = num;
            min_count = 0;
        }
        if (num == min){
            min_count += 1;
        }
        location = ftell(file);  // Remember where we are
      } else {
        // Go back to where we were plus 1.
        location++;
        if (fseek(file, location, SEEK_SET) < 0) {
          break;
        }
        // This is better than simply consuming a single character as the
        // location in the file may not be where is was due to a fail conversion.
        // C only specifies being able to back up at least 1 after failure
        // and input like "+-1" might back up 1 or 2 places.
      }
    }

If I was truly going hard core, I think I would instead:

    // Read one line at a time. 
    char buf[BUFSIZ + 1]; // Lines much bigger than this are already a problem for `scanf()`.
    while (fgets(buf, sizeof buf, file)) {
      char *p = buf;
   
      while (*p) {
        char *endptr;
        errno = 0;
        long long num = strtoll(p, &endptr, /* base */ 10);
        if (endptr > p) {
          // Success
          if (errno) Handle_Overflow()
          else  {
            // Perform metric on `num`.
            ...
          } 
          p = endptr;  // Continue where `strtoll()` stopped.
        } else {
          p++; Go to next character.
        }
      }
    }

If I was truly going beyond, I think I would instead go for a state machine.

Note, such detail is prone to coding failure.

char sign = '+';
int ch;
while ((ch = fgetc(file)) != EOF) {
  if (ch == '-' || ch = '+') {
    sign = ch;
    continue;
  }
  if (!isdigit(ch)) {
    char sign = '+';
    continue;
  }
  uintmax_t num = ch - '0';
  while (isdigit((ch = fgetc(file))) {
    // Maybe add code here to detect overflow.
    num = num * 10 + ch - '0';
  }
  // Do metrics on sign,num
  ...
  ungetch(ch, file);  // Put back non-numeric digit
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.