fxlibc/src/libc/stdlib/strto_int.c

107 lines
2.5 KiB
C

#include "stdlib_p.h"
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
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;
}