cLK/lk/lib/libc/string/strtoul.c
2011-11-06 23:17:49 +00:00

174 lines
4.0 KiB
C

/*
* Copyright (C) 2011 Rick_1995@xda-developers.com
*
* Notes:
* The primary objective of this implementation was minimal size.
* Note: Assumes char layout 0-9.*A-Z.*a-z for ordinals values.
* There are a couple of compile-time options below.
*/
/*****************************************************************************/
/* OPTIONS */
/*****************************************************************************/
/* Set if we want strtod to set errno appropriately. */
/* NOTE: Implies _STRTO_ENDPTR below */
#define _STRTO_ERRNO 0
/* Set if we want support for the endptr arg. */
/* Implied by _STRTO_ERRNO. */
#define _STRTO_ENDPTR 1
/*****************************************************************************/
/* Don't change anything that follows. */
/*****************************************************************************/
#if _STRTO_ERRNO
#undef _STRTO_ENDPTR
#define _STRTO_ENDPTR 1
#endif
/*****************************************************************************/
/* Are there actually an machines where this might fail? */
#if 'A' > 'a'
#error ordering assumption violated : 'A' > 'a'
#endif
#include <stdlib.h>
#include <limits.h>
#include <ctype.h>
#if _STRTO_ERRNO
#include <errno.h>
#endif
/*
* This is the main work fuction which handles both strtol (uflag = 0) and
* strtoul (uflag = 1).
*/
unsigned long _strto_l(const char *str, char **endptr, int base, int uflag)
{
unsigned long number = 0;
unsigned long cutoff;
char *pos = (char *) str;
#if _STRTO_ENDPTR
char *fail_char = (char *) str;
#endif
int digit, cutoff_digit;
int negative;
while (isspace(*pos)) { /* skip leading whitespace */
++pos;
}
/* handle optional sign */
negative = 0;
switch(*pos) {
case '-': negative = 1; /* fall through to increment pos */
case '+': ++pos;
}
if ((base == 16) && (*pos == '0')) { /* handle option prefix */
++pos;
#if _STRTO_ENDPTR
fail_char = pos;
#endif
if ((*pos == 'x') || (*pos == 'X')) {
++pos;
}
}
if (base == 0) { /* dynamic base */
base = 10; /* default is 10 */
if (*pos == '0') {
++pos;
base -= 2; /* now base is 8 (or 16) */
#if _STRTO_ENDPTR
fail_char = pos;
#endif
if ((*pos == 'x') || (*pos == 'X')) {
base += 8; /* base is 16 */
++pos;
}
}
}
if ((base < 2) || (base > 36)) { /* illegal base */
goto DONE;
}
cutoff = ULONG_MAX / base;
cutoff_digit = ULONG_MAX - cutoff * base;
while (1) {
digit = 40;
if ((*pos >= '0') && (*pos <= '9')) {
digit = (*pos - '0');
} else if (*pos >= 'a') {
digit = (*pos - 'a' + 10);
} else if (*pos >= 'A') {
digit = (*pos - 'A' + 10);
} else break;
if (digit >= base) {
break;
}
++pos;
#if _STRTO_ENDPTR
fail_char = pos;
#endif
/* adjust number, with overflow check */
if ((number > cutoff)
|| ((number == cutoff) && (digit > cutoff_digit))) {
number = ULONG_MAX;
if (uflag) {
negative = 0; /* since unsigned returns ULONG_MAX */
}
#if _STRTO_ERRNO
errno = ERANGE;
#endif
} else {
number = number * base + digit;
}
}
DONE:
#if _STRTO_ENDPTR
if (endptr) {
*endptr = fail_char;
}
#endif
if (negative) {
if (!uflag && (number > ((unsigned long)(-(1+LONG_MIN)))+1)) {
#if _STRTO_ERRNO
errno = ERANGE;
#endif
return (unsigned long) LONG_MIN;
}
return (unsigned long)(-((long)number));
} else {
if (!uflag && (number > (unsigned long) LONG_MAX)) {
#if _STRTO_ERRNO
errno = ERANGE;
#endif
return LONG_MAX;
}
return number;
}
}
unsigned long strtoul(const char *str, char **endptr, int base)
{
return _strto_l(str, endptr, base, 1);
}
long strtol(const char *str, char **endptr, int base)
{
return _strto_l(str, endptr, base, 0);
}