spaffy spaffy - 4 months ago 11
C Question

Converting char array to (negative) integer; does not compile because of "array - '0'" line

So I've got this code:

#include <stdio.h>

int main(void) {
char arr[BUFSIZ];
arr[BUFSIZ - 1] = '\0';
fgets(arr, sizeof(arr), stdin);
int x = arr - '0';
printf("%d\n", x);
}


and when I try to compile it, using clang's make, I get this error:

$ make test
clang -ggdb3 -O0 -std=c99 -Wall -Werror test.c -lm -o test
test.c:9:9: error: incompatible pointer to integer conversion initializing 'int'
with an expression of type 'char *' [-Werror,-Wint-conversion]
int x = arr - '0';
^ ~~~~~~~~~
1 error generated.


It doesn't matter if I comment or uncomment the
arr[BUFSIZ - 1] = '\0';
line, this just won't compile.

I've read that
int x = arr - '0';
should do the trick, but it does not..

Answer

The expected user input for your program is a string of characters that represents digits, possibly prefixed with a minus sign.

Let's assume at first that the user gives an expected input.

This input is stored in a char array of size SIZE:

char arr[SIZE];
fgets(arr, SIZE, stdin);

At this point, your array arr holds an address (for exemple 0xDA7A1234) where the content of your array resides. If you try to get a value in arr, you need to use the [] operator. That's why your int x = arr - '0' doesn't work. It takes the address in arr, then substract it with the char '0' (which is the same as substracting 48, by the way).

Let's say your input is '4' '5' '6' '7'. arr equals 0xDA7A1234, arr[0] (ie 0xDA7A1234) equals '4', arr[1] (0xDA7A1235) equals '5', and so on. '4' is an ascii character. It is a representation of a character, but really to C it is just a numeric value. The ascii table, which is a standard, gives the matching numeric value to a given character (here is an ascii table: http://www.asciitable.com/index/asciifull.gif). In this table the decimal value matching '4' is 52. The decimal value matching '0' is 48. So '4' - '0' is really just 52 - 48 = 4. Since the digits are consecutive in the ascii table, substracting a digit character with the '0' character WILL give you, perchance (or by design I don't now), the correct digit in integer representation. However, if there is a minus sign (such as '-' '1' '2' '4'), the operation you will perform is '-' - '0', which according to the ASCII table, is really 45 - 48, which yields -3, as observed (it is not linked with the fact that your input contained 3 as the last digit).

This means you have to make a special case for the '-' character. For instance:

char is_negative = 0; // 0 means that it is a positive integer,
                      // 1 a negative integer  
if (arr[0] == '-') {
    is_negative = 1;
}
// ... We retrieve the other digits in x
if (is_negative == 1) {
    x *= -1; // Let's make x a negative integer
}

With this sorted out, you need a means to actually retrieve the others digits (you can't just use the first one, especially if it is just a '-').

To do this, you need to look at each character in you array, using a loop.

 int i;
 for (i = 1; i < SIZE; ++i) {
      int digit = arr[i] - '0';
      x *= 10; // shift x by a power of ten
      x += digit; // add the newly retrieved digit.
 }

There's a problem with the loop above. Indeed, your user MIGHT enter an integer that has not SIZE-1 number of digits. What would happen then? The program will look for values in arr that are not initialized, which mean that they will appear random (their value is not defined by the C standard, so it could be anything). Even if your array has the maximum length, a C string is null terminated, meaning the character '\0' is at the end of the array, so it will mess up anyway.

To fix that we need to get the actual "useful" size of our array, that is, the string length. To achieve this I use the string function strlen, that returns the length of the string without counting the null terminating character ('\0').

// let's get the actual size of the array
size_t len = strlen(arr);

// our loop becomes
for (i = 1; i < len; ++i) {
    int digit = arr[i] - '0';
    x *= 10; // shift x by a power of ten
    x += digit; // add the newly retrieved digit.
}

However, there is STILL a bug. Indeed, our string contains a hidden character that gets counted! As you press return to validate your entry, the new line character '\n' is appended to the line (decimal ASCII code 10). You don't want this, so the correct loop is:

for (i = 1; i < len-1; ++i) {
    int digit = arr[i] - '0';
    x *= 10; // shift x by a power of ten
    x += digit; // add the newly retrieved digit.
}

Notice the len-1!

So, here would be your full example:

#define SIZE 8
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(void) {
    char arr[SIZE];
    fgets(arr, SIZE, stdin);
    // let's get the actual size of the array
    size_t len = strlen(arr);
    char is_negative = 0; // 0 means that it is a positive integer,
    // 1 a negative integer  
    int x = 0; // The integer whose retrieval is our quest
    if (arr[0] == '-') {
        is_negative = 1;
    } else {
        x = arr[0] - '0';
    }
    // ... We retrieve the other digits in x
    int i;
    for (i = 1; i < len-1; ++i) {
        int digit = arr[i] - '0';
        x *= 10; // shift x by a power of ten
        x += digit; // add the newly retrieved digit.
    }
    if (is_negative == 1) {
        x *= -1; // Let's make x a negative integer
    }
    printf("%d\n", x);
    return 0; // A program that terminates correctly should return 0.
}

This example assumes that your user entered a valid number (possibly prefixed with a minus sign), but NOTHING prevents your user from entering other values, and it will yields gibberish output.