37

If I want to convert a single numeric char to it's numeric value, for example, if:

char c = '5';

and I want c to hold 5 instead of '5', is it 100% portable doing it like this?

c = c - '0';

I heard that all character sets store the numbers in consecutive order so I assume so, but I'd like to know if there is an organized library function to do this conversion, and how it is done conventionally. I'm a real beginner :)

Binary Worrier
  • 50,774
  • 20
  • 136
  • 184
Ori Popowski
  • 10,432
  • 15
  • 57
  • 79
  • @Binary- The duplicate posted is for C++, even though the answer is the same, it applies to a different language – TStamper Apr 23 '09 at 14:04

10 Answers10

28

Yes, this is a safe conversion. C requires it to work. This guarantee is in section 5.2.1 paragraph 2 of the latest ISO C standard, a recent draft of which is N1570:

Both the basic source and basic execution character sets shall have the following members:
[...]
the 10 decimal digits
0 1 2 3 4 5 6 7 8 9
[...]
In both the source and execution basic character sets, the value of each character after 0 in the above list of decimal digits shall be one greater than the value of the previous.

Both ASCII and EBCDIC, and character sets derived from them, satisfy this requirement, which is why the C standard was able to impose it. Note that letters are not contiguous iN EBCDIC, and C doesn't require them to be.

There is no library function to do it for a single char, you would need to build a string first:

int digit_to_int(char d)
{
 char str[2];

 str[0] = d;
 str[1] = '\0';
 return (int) strtol(str, NULL, 10);
}

You could also use the atoi() function to do the conversion, once you have a string, but strtol() is better and safer.

As commenters have pointed out though, it is extreme overkill to call a function to do this conversion; your initial approach to subtract '0' is the proper way of doing this. I just wanted to show how the recommended standard approach of converting a number as a string to a "true" number would be used, here.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
unwind
  • 391,730
  • 64
  • 469
  • 606
  • 4
    This is an unecceasrily complex and expensive way to do what is a very straight forward operation i.e. int i = d - '0' – Binary Worrier Apr 23 '09 at 13:47
  • 2
    This is an answer to your question but not the best answer. This is overkill. You answered your own question with c=c-'0'; - that is the best answer. – zooropa Apr 23 '09 at 13:56
  • 1
    Yes, of course using a function is overkill. Perhaps I should have been clearer on that part. – unwind Apr 23 '09 at 14:32
  • The overkill isn't just the function aspect. Using strtol is overkill as well. – zooropa Apr 23 '09 at 20:59
  • 1
    This answer doesn't address the portability aspect of this question. This function isn't portable. It wouldn't work on the multi-byte character sets. The char d parameter would produce compiler errors when variables of type wchar_t are attempted to be passed in. – zooropa Apr 24 '09 at 13:07
9

Try this :

char c = '5' - '0';
Martin.
  • 10,494
  • 3
  • 42
  • 68
lsalamon
  • 7,998
  • 6
  • 50
  • 63
6
int i = c - '0';

You should be aware that this doesn't perform any validation against the character - for example, if the character was 'a' then you would get 91 - 48 = 49. Especially if you are dealing with user or network input, you should probably perform validation to avoid bad behavior in your program. Just check the range:

if ('0' <= c &&  c <= '9') {
    i = c - '0';
} else {
    /* handle error */
}

Note that if you want your conversion to handle hex digits you can check the range and perform the appropriate calculation.

if ('0' <= c && c <= '9') {
    i = c - '0';
} else if ('a' <= c && c <= 'f') {
    i = 10 + c - 'a';
} else if ('A' <= c && c <= 'F') {
    i = 10 + c - 'A';
} else {
    /* handle error */
}

That will convert a single hex character, upper or lowercase independent, into an integer.

Mac
  • 14,615
  • 9
  • 62
  • 80
spinfire
  • 2,181
  • 1
  • 11
  • 4
  • ctype.h provides the following function/macros for performing the validation: isdigit, isxdigit, isspace, and many others. See the man page. – James Morris Nov 24 '12 at 12:41
5

You can use atoi, which is part of the standard library.

nalzok
  • 14,965
  • 21
  • 72
  • 139
weloytty
  • 5,808
  • 5
  • 28
  • 35
3

Since you're only converting one character, the function atoi() is overkill. atoi() is useful if you are converting string representations of numbers. The other posts have given examples of this. If I read your post correctly, you are only converting one numeric character. So, you are only going to convert a character that is the range 0 to 9. In the case of only converting one numeric character, your suggestion to subtract '0' will give you the result you want. The reason why this works is because ASCII values are consecutive (like you said). So, subtracting the ASCII value of 0 (ASCII value 48 - see ASCII Table for values) from a numeric character will give the value of the number. So, your example of c = c - '0' where c = '5', what is really happening is 53 (the ASCII value of 5) - 48 (the ASCII value of 0) = 5.

When I first posted this answer, I didn't take into consideration your comment about being 100% portable between different character sets. I did some further looking around around and it seems like your answer is still mostly correct. The problem is that you are using a char which is an 8-bit data type. Which wouldn't work with all character types. Read this article by Joel Spolsky on Unicode for a lot more information on Unicode. In this article, he says that he uses wchar_t for characters. This has worked well for him and he publishes his web site in 29 languages. So, you would need to change your char to a wchar_t. Other than that, he says that the character under value 127 and below are basically the same. This would include characters that represent numbers. This means the basic math you proposed should work for what you were trying to achieve.

zooropa
  • 3,929
  • 8
  • 39
  • 61
1

Yes. This is safe as long as you are using standard ascii characters, like you are in this example.

Joe Corkery
  • 2,564
  • 3
  • 18
  • 26
0

As others have suggested, but wrapped in a function:

int char_to_digit(char c) {
    return c - '0';
}

Now just use the function. If, down the line, you decide to use a different method, you just need to change the implementation (performance, charset differences, whatever), you wont need to change the callers.

This version assumes that c contains a char which represents a digit. You can check that before calling the function, using ctype.h's isdigit function.

MyName
  • 2,136
  • 5
  • 26
  • 37
0

Since the ASCII codes for '0','1','2'.... are placed from 48 to 57 they are essentially continuous. Now the arithmetic operations require conversion of char datatype to int datatype.Hence what you are basically doing is: 53-48 and hence it stores the value 5 with which you can do any integer operations.Note that while converting back from int to char the compiler gives no error but just performs a modulo 256 operation to put the value in its acceptable range

chinmay
  • 850
  • 2
  • 8
  • 16
0

You can simply use theatol()function:

#include <stdio.h>
#include <stdlib.h>

int main() 
{
    const char *c = "5";
    int d = atol(c);
    printf("%d\n", d);

}
0

Normally, if there's no guarantee that your input is in the '0'..'9' range, you'd have to perform a check like this:

if (c >= '0' && c <= '9') {
    int v = c - '0';
    // safely use v
}

An alternative is to use a lookup table. You get simple range checking and conversion with less (and possibly faster) code:

// one-time setup of an array of 256 integers;
// all slots set to -1 except for ones corresponding
// to the numeric characters
static const int CHAR_TO_NUMBER[] = {
    -1, -1, -1, ...,
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, // '0'..'9'
    -1, -1, -1, ...
};

// Now, all you need is:

int v = CHAR_TO_NUMBER[c];

if (v != -1) {
    // safely use v
}

P.S. I know that this is an overkill. I just wanted to present it as an alternative solution that may not be immediately evident.

Ates Goral
  • 137,716
  • 26
  • 137
  • 190