JoM
JoM

Reputation: 13

Unexpected result when using atoi() on content of file, read char by char

I have a text file in which there are only numbers (0, 1, 2 & 3) and I want to process the data to know how many times each number appears.

The following program works with small text file (<100 numbers) but with bigger files (I need to process in the end several thousands of data) the program reads numbers that are not in the text file.

Here's my code :

FILE *file;
char c;
int nb;
int th0 = 0, th1 = 0, th2 = 0, th3 = 0;

file = fopen("../data", "r");

if (file == NULL) {
    printf("ERROR FILE: %s", strerror(errno));
    return EXIT_FAILURE;
}

while (1) {
    if (fscanf(file, "%c", &c) == EOF)
        break;

    nb = atoi(&c);

    printf("%d", nb);

    switch (nb) {
      case 0:
        th0++;
        break;

      case 1:
        th1++;
        break;

      case 2:
        th2++;
        break;

      case 3:
        th3++;
        break;

      default:
        break;
    }
}

I would appreciate any suggestions.

EDIT, the input text with the output :

Data file (181 numbers):

001110120101010102012012021201021202102012012012012010210210210120120230103120130230123201320310231023102302301231203213210131032103210230120320310320213202301320123120312302321023

Output: The end of the reading is not what is in the data file and count 156 numbers

Upvotes: 1

Views: 473

Answers (3)

chqrlie
chqrlie

Reputation: 144949

You call atoi with a pointer to char that does not point to a proper C string, hence your code has undefined behavior and may seem to work as expected sometimes and misbehave at other times...

Since you are dealing with digits, you could compute the value represented by the digit with a simple subtraction c - '0'.

You could also simplify the code with an array:

#include <stdio.h>

int main() {
    FILE *file;
    int c, i;
    int count[10] = { 0 };

    file = fopen("../data", "r");
    if (file == NULL) {
        printf("ERROR FILE: %s", strerror(errno));
        return EXIT_FAILURE;
    }

    while ((c = getc(file)) != EOF) {
        if (c >= '0' && c <= '9')
            count[c - '0']++;
    }
    fclose(file);

    printf("counts:\n");
    for (i = 0; i < 10; i++) {
        printf("%d: %d\n", i, count[i]);
    }
    return 0;
}

Upvotes: 0

Chris Turner
Chris Turner

Reputation: 8142

Your problem is that atoi expects a string and you're calling it like this:

nb = atoi(&c);

with c being just a char. Sometimes that might work, but you're basically hitting undefined behaviour as you can't guarantee that the memory after c is empty.

Instead you want to calculate nb differently.

nb = c - '0';

This relies on the fact that in the ASCII table, the numbers 0 to 9 are in a block together. Subtracting the value of '0' from c will get you the numerical value of that character...assuming it is a digit.

And just to ensure it is a digit you should wrap this if statement around your code

if(isdigit(c)) // Check if c is a digit
    {
    nb = c - '0';

    switch(nb) {
        case 0: th0++;
        break;
    // rest of switch goes here....
         }
    }

Upvotes: 4

Yunnosch
Yunnosch

Reputation: 26753

Looking at

https://en.cppreference.com/w/c/string/byte/atoi

I see

Parameters
str - pointer to the null-terminated byte string to be interpreted

But with

char c;
/* ... */
nb = atoi(&c);

you are using a pointer to a single char, which is followed by who-knows-what.
For anything which happens to not be a '\0' you will get a result from atoi() which is

a) based on access beyond the intended variable
b) for following digits, is a two-digit number or higher

The first option means that your code needs fixing. The second option can explain any number > 9.

Upvotes: 3

Related Questions