2009-03-26

ASCII isdigit, isalpha and isxdigit macros in ANSI C with arithmetic operations

The naïve way of defining a C macro which tests whether a character is a digit (assuming an ASCII-based character set) is #define ISDIGIT(c) ((c) >= '0' && (c) <= '9'). There is a fundamental problem with this naïve definition: it evaluates its argument c more than once, so for example ISDIGIT(x++) will increment x by 2 in some cases. The question naturally arises if there is a macro definition #define ISDIGIT(c) ..., which uses c exactly once. Indeed, there is:
#define ISDIGIT(c) ((c) - '0' + 0U <= 9U). The capital Us in the expression enforce unsigned calculation, so if c is less than '0', then (c) - '0' + 0U becomes a large positive number instead of a negative number with small absolute value, so the comparison will (correctly) return false.

The following code contains solutions for ISDIGIT, ISALPHA and ISXDIGIT, the latter not being practical because of the excessive use of arithmetic operations.
/* by pts@fazekas.hu at Thu Mar 26 01:10:56 CET 2009
*
* 0123456789 ISDIGIT
* ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ISALPHA
* 0123456789ABCDEF ISCAPITALHEX
* 0123456789ABCDEFabcdef ISXDIGIT
*/
#include <stdio.h>

#define ISDIGIT(c) ((c) - '0' + 0U <= 9U)
#define ISALPHA(c) (((c) | 32) - 'a' + 0U <= 'z' - 'a' + 0U)
#define ISCAPITALHEX(c) ((((((c) - 48U) & 255) * 23 / 22 + 4) / 7 ^ 1) <= 2U)
#define ISXDIGIT(c) (((((((((c) - 48U) & 255) * 18 / 17 * 52 / 51 * 58 / 114 \
* 13 / 11 * 14 / 13 * 35 + 35) / 36 * 35 / 33 * 34 / 33 * 35 / 170 ^ 4) \
- 3) & 255) ^ 1) <= 2U)

int main(int argc, char **argv) {
int i;
(void)argc; (void)argv;
for (i = 0; i < 256; ++i) if (ISDIGIT(i)) putchar(i);
printf(" ISDIGIT\n");
for (i = 0; i < 256; ++i) if (ISALPHA(i)) putchar(i);
printf(" ISALPHA\n");
for (i = 0; i < 256; ++i) if (ISCAPITALHEX(i)) putchar(i);
printf(" ISCAPITALHEX\n");
for (i = 0; i < 256; ++i) if (ISXDIGIT(i)) putchar(i);
printf(" ISXDIGIT\n");
return 0;
}

No comments: