Skip to content

Instantly share code, notes, and snippets.

@johnbartholomew
Created March 6, 2013 12:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johnbartholomew/5098937 to your computer and use it in GitHub Desktop.
Save johnbartholomew/5098937 to your computer and use it in GitHub Desktop.
str2long function with correct range checks.
/* 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