Created
March 6, 2013 12:20
-
-
Save johnbartholomew/5098937 to your computer and use it in GitHub Desktop.
str2long function with correct range checks.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Written for | |
* | |
* A Quick Coding Contest: Convert String to Integer Without Overflow | |
* | |
* http://blog.regehr.org/archives/909 | |
* | |
* This has not yet been run through clang's integer behaviour sanitizer, | |
* because I don't have a recent enough clang build. | |
*/ | |
#include <limits.h> | |
extern int error; | |
long str2long(const char *s); | |
long str2long_x(const char *s, int *err); | |
long str2long(const char *s) { | |
return str2long_x(s, &error); | |
} | |
#if 0 && defined(BUILD_TESTS) && BUILD_TESTS | |
#define STR2LONG_MAX INT_MAX | |
#define STR2LONG_MIN INT_MIN | |
#define STR2LONG_TYPE int | |
#else | |
#define STR2LONG_MAX LONG_MAX | |
#define STR2LONG_MIN LONG_MIN | |
#define STR2LONG_TYPE long | |
#endif | |
long str2long_x(const char *s, int *err) { | |
unsigned STR2LONG_TYPE value = 0, max = STR2LONG_MAX; | |
int neg = 0; | |
if (*s == '-') { | |
neg = 1; | |
/* if you know you're on a two's complement machine you could do | |
* max = (unsigned STR2LONG_TYPE)STR2LONG_MAX + 1; | |
*/ | |
max = -(unsigned STR2LONG_TYPE)STR2LONG_MIN; | |
++s; | |
} | |
/* need at least one digit */ | |
if (*s < '0' || *s > '9') goto bad_input; | |
while (*s >= '0' && *s <= '9') { | |
const unsigned STR2LONG_TYPE next = value*10 + (unsigned STR2LONG_TYPE)(*s++ - '0'); | |
if (next < value) goto bad_input; | |
value = next; | |
} | |
/* no extra characters, and value must be in range */ | |
if ((*s != '\0') || (value > max)) goto bad_input; | |
*err = 0; | |
return (STR2LONG_TYPE)(neg ? -value : value); | |
bad_input: | |
*err = 1; | |
return 0; | |
} | |
#if defined(BUILD_TESTS) && BUILD_TESTS | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <assert.h> | |
static int print_successes; | |
static long test_count, fail_count; | |
typedef __int128 bigint; | |
static void bigint2str(char *buf, bigint x) { | |
char *s; | |
if (x < 0) { | |
*buf++ = '-'; | |
x = -x; | |
} | |
s = buf; | |
if (x) { | |
do { | |
*s++ = '0' + (x % 10); | |
x /= 10; | |
} while (x); | |
} else { | |
*s++ = '0'; | |
} | |
*s-- = '\0'; | |
while (buf < s) { | |
char a = *buf, b = *s; | |
*buf = b; | |
*s = a; | |
++buf; --s; | |
} | |
} | |
static void test_value(bigint x) { | |
char xbuf[48], ybuf[48]; | |
int err, ok; | |
bigint y; | |
bigint2str(xbuf, x); | |
y = str2long_x(xbuf, &err); | |
bigint2str(ybuf, y); | |
ok = (x >= (bigint)STR2LONG_MIN && x <= (bigint)STR2LONG_MAX) ? ((x == y) && !err) : err; | |
if (ok) { | |
if (print_successes) { | |
printf("OK: x = %s%s\n", xbuf, err ? " (out of range)" : ""); | |
} | |
} else { | |
printf("FAIL: x = %s; y = %s; err = %d\n", xbuf, ybuf, err); | |
++fail_count; | |
} | |
++test_count; | |
} | |
static void test_range(bigint from, bigint to) { | |
bigint x; | |
assert(from < to); /* from cannot be LONG_MAX; to cannot be LONG_MIN */ | |
for (x = from; x <= to; ++x) { test_value(x); } | |
} | |
static void test_invalid(const char *str) { | |
STR2LONG_TYPE y; | |
int err; | |
y = str2long_x(str, &err); | |
if (err) { | |
if (print_successes) { | |
printf("OK: %s (invalid)\n", str); | |
} | |
} else { | |
printf("FAIL: x = %s; y = %ld; err = %d\n", str, y, err); | |
++fail_count; | |
} | |
++test_count; | |
} | |
int main(void) { | |
const bigint offset = 100000; | |
print_successes = 0; | |
printf("Testing normal behaviour for range [-offset, offset]...\n"); | |
test_range(-offset, offset); | |
printf("Testing normal behaviour for range [STR2LONG_MIN, STR2LONG_MIN + offset]...\n"); | |
test_range((bigint)STR2LONG_MIN, (bigint)STR2LONG_MIN + offset); | |
printf("Testing normal behaviour for range [STR2LONG_MAX - offset, STR2LONG_MAX]...\n"); | |
test_range((bigint)STR2LONG_MAX - offset, (bigint)STR2LONG_MAX); | |
printf("Testing out-of-range behaviour for range [STR2LONG_MIN - offset, STR2LONG_MIN]...\n"); | |
test_range((bigint)STR2LONG_MIN - offset, (bigint)STR2LONG_MIN); | |
printf("Testing out-of-range behaviour for range [STR2LONG_MAX, STR2LONG_MAX + offset]...\n"); | |
test_range((bigint)STR2LONG_MAX, (bigint)STR2LONG_MAX + offset); | |
printf("Testing behaviour for non-numeric text...\n"); | |
test_invalid(""); | |
test_invalid("hello"); | |
test_invalid("-"); | |
test_invalid("x"); | |
test_invalid(" 12345"); | |
test_invalid("12345 "); | |
test_invalid("- 12345"); | |
test_invalid("-12345 "); | |
test_invalid("12 345"); | |
test_invalid("-12 345"); | |
printf("FINISHED: %ld / %ld failed\n", fail_count, test_count); | |
return 0; | |
} | |
#endif /* BUILD_TESTS */ | |
/* vim: set ts=8 sts=3 sw=3 et ai ci: */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment