fxlibc/src/libc/stdlib/strto_int.c

114 lines
2.9 KiB
C

#include "stdlib_p.h"
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
/* Parse an integer from a string. Base function for strtol, strtoul, strtoll,
and strtoull. This function:
-> Does not set errno, and instead return the potential error code. Setting
errno would prevent these functions from calling each other as they all
have different ranges, resulting in undue ERANGE.
-> Can parse into both long and long long, depending on what pointer is
non-NULL. */
int strto_int(char const * restrict ptr, char ** restrict endptr, int base,
long *outl, long long *outll, bool use_unsigned)
{
/* Save the value of ptr in endptr now in case the format is invalid */
if(endptr) *endptr = (char *)ptr;
/* Skip initial whitespace */
while(isspace(*ptr)) ptr++;
/* Accept a sign character */
bool negative = false;
if(*ptr == '-') negative = true;
if(*ptr == '-' || *ptr == '+') ptr++;
/* Use unsigned variables as only these have defined overflow */
unsigned long xl = 0;
unsigned long long xll = 0;
int errno_value = 0;
bool valid = false;
/* Read prefixes and determine base */
if((base == 0 || base == 16) && ptr[0]=='0' && tolower(ptr[1])=='x') {
ptr += 2;
base = 16;
}
else if(base == 0 && ptr[0] == '0') {
ptr++;
base = 8;
}
else if(base == 0) {
base = 10;
}
/* Read digits */
while(1) {
int v = -1;
if(isdigit(*ptr)) v = *ptr - '0';
if(islower(*ptr)) v = *ptr - 'a' + 10;
if(v == -1 || v >= base) break;
/* The value is valid as long as there is at least one digit */
valid = true;
/* (x = base*x + v) but with overflow checks */
if(outl) {
if(__builtin_umull_overflow(xl, base, &xl))
errno_value = ERANGE;
if(__builtin_uaddl_overflow(xl, v, &xl))
errno_value = ERANGE;
}
if(outll) {
if(__builtin_umulll_overflow(xll, base, &xll))
errno_value = ERANGE;
if(__builtin_uaddll_overflow(xll, v, &xll))
errno_value = ERANGE;
}
ptr++;
}
/* Handle sign and range */
if(negative) {
/* Only -0 can be represented as unsigned */
if(outl && use_unsigned && xl != 0)
errno_value = ERANGE;
if(outll && use_unsigned && xll != 0)
errno_value = ERANGE;
/* Handle signed range. Don't use -[L]LONG_MIN, it overflows */
if(outl && !use_unsigned && xl > LONG_MAX * 1ul + 1)
errno_value = ERANGE;
if(outll && !use_unsigned && xll > LLONG_MAX * 1ull + 1)
errno_value = ERANGE;
xl = -xl;
xll = -xll;
}
else {
if(outl && !use_unsigned && xl > LONG_MAX * 1ul)
errno_value = ERANGE;
if(outll && !use_unsigned && xll > LLONG_MAX * 1ull)
errno_value = ERANGE;
}
/* Handle overflows */
if(outl && errno_value == ERANGE) {
if(use_unsigned) xl = ULONG_MAX;
else xl = negative ? LONG_MIN : LONG_MAX;
}
if(outll && errno_value == ERANGE) {
if(use_unsigned) xll = ULLONG_MAX;
else xll = negative ? LLONG_MIN : LLONG_MAX;
}
if(outl) *outl = xl;
if(outll) *outll = xll;
if(endptr && valid) *endptr = (char *)ptr;
return errno_value;
}