[uClibc]new version of strtol and strtoul

Manuel Novoa III mnovoa3 at bellsouth.net
Wed Dec 13 05:06:57 UTC 2000


Erik,

Here's an implementation of strtol and strtoul (trickier that I thought).
It seemed best to implement one worker function to handle both.
I didn't start out that way, but then I found out that it strtoul
was expected to handle a sign.  Most unintuitive...

Anyway, I ran test/stdlib/teststrtol.c for this (endptr) implementation
and the implementation in libc-2.1.3.  The output was identical.

Here are some sizes for i386 -Os -fomit-frame-pointer.

Current versions:
   text    data     bss     dec     hex filename
    575       0       0     575     23f strtol.o
    539       0       0     539     21b strtoul.o
   1114 total

New version:
   text    data     bss     dec     hex filename
    351       0       0     351     15f strto_l.o  (no endptr or errno)
    383       0       0     383     17f strto_l.o  (endptr but no errno)
    415       0       0     415     19f strto_l.o  (endptr and errno)

So I have a full implementation of both functions in less space than each
one currently takes.  You could even save a few more bytes if you implemented
strtol and strtoul as inlines in stdlib.h.

New version minus the mini functions for strtol and strtoul.
   text    data     bss     dec     hex filename
    304       0       0     304     130 strto_l.o  (no endptr or errno)
    336       0       0     336     150 strto_l.o  (endptr but no errno)
    366       0       0     366     16e strto_l.o  (endptr and errno)

Manuel

------------------------ stdlib/strto_l.c -------------------------------

/*
 * Copyright (C) 2000 Manuel Novoa III
 *
 * 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);
}





More information about the uClibc mailing list