feat(stdlib): numeric string conversions

This commit is contained in:
2026-09-03 21:53:59 -04:00
parent bcb4e259c3
commit 26786e9027
6 changed files with 2036 additions and 0 deletions
+1028
View File
File diff suppressed because it is too large Load Diff
+149
View File
@@ -0,0 +1,149 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stddef.h>
#include <limits.h>
#include "../internal/strtox.h"
/*
* Shared integer subject-sequence scan (todo 11). See src/internal/strtox.h
* for the contract; this file is the engine the strtol/strtoul wrappers and
* todo 14's strtoimax/strtoumax build on.
*
* Prefix rules (C23 7.24.1.4p3-4):
* - base 0: "0x"/"0X" followed by a hex digit selects base 16 and the
* prefix is consumed; a bare leading 0 selects base 8 and the 0 is the
* subject itself ("0" is a valid integer constant, so the subject is
* never empty after an octal prefix); anything else is base 10.
* - base 16: "0x"/"0X" followed by a hex digit is consumed; without a
* hex digit the "0" is itself a valid hex digit and the x terminates
* the subject.
* - "0x" without a hex digit under base 0 is no integer-constant form,
* so there is no subject sequence at all.
*
* Endptr always lands at the first character that is not part of the
* subject sequence, or at nptr when there is no subject (C23 7.24.1.4p5,
* even when whitespace or a sign was consumed).
*/
int
__strtox_scan(const char *nptr, char **endptr, int base, int *neg, unsigned long long *mag,
int *over)
{
const char *s = nptr;
int sg = 0;
int ov = 0;
int digits = 0;
int consumed_prefix = 0;
unsigned long long m = 0;
while (strtox_isspace(*s))
{
s++;
}
if (*s == '+' || *s == '-')
{
sg = (*s == '-');
s++;
}
if (base != 0 && (base < 2 || base > 36))
{
if (endptr)
{
*endptr = (char *)nptr;
}
*neg = 0;
*mag = 0;
*over = 0;
return STRTOX_BADBASE;
}
if (base == 0 || base == 16)
{
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
{
int d2 = strtox_digit(s[2]);
if (d2 >= 0 && d2 < 16)
{
base = 16;
s += 2;
}
else if (base == 0)
{
/* "0x" with no hex digit is no integer-constant form. */
if (endptr)
{
*endptr = (char *)nptr;
}
*neg = 0;
*mag = 0;
*over = 0;
return STRTOX_EMPTY;
}
/* base 16: the 0 itself is the subject; the x ends it. */
}
else if (base == 0 && s[0] == '0')
{
/* Octal prefix: the 0 opens the subject even when no octal
* digit follows ("0" is itself a valid integer constant). */
base = 8;
consumed_prefix = 1;
s++;
}
else if (base == 0)
{
/* No prefix: a decimal subject (base 0 auto-detection). */
base = 10;
}
}
for (;;)
{
int d = strtox_digit(*s);
if (d < 0 || d >= base)
{
break;
}
if (ov == 0)
{
unsigned long long lim =
(ULLONG_MAX - (unsigned long long)d) / (unsigned long long)base;
if (m > lim)
{
ov = 1;
}
else
{
m = (m * (unsigned long long)base) + (unsigned long long)d;
}
}
digits++;
s++;
}
if (digits == 0 && consumed_prefix == 0)
{
if (endptr)
{
*endptr = (char *)nptr;
}
*neg = 0;
*mag = 0;
*over = 0;
return STRTOX_EMPTY;
}
if (endptr)
{
*endptr = (char *)s;
}
*neg = sg;
*mag = m;
*over = ov;
return STRTOX_OK;
}
+143
View File
@@ -0,0 +1,143 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include "../internal/strtox.h"
/*
* Public integer wrappers over the __strtox_scan engine (todo 11).
*
* Clamping policy (C23 7.24.1.4p7-8):
* - signed: a magnitude above the type maximum (with the sign folded in:
* -2^(N-1) is representable, -(2^(N-1)+1) is not) clamps to LONG_MAX /
* LONG_MIN with errno ERANGE;
* - unsigned: a magnitude above the type maximum clamps to ULONG_MAX
* with errno ERANGE; a minus sign negates the value in the return type
* (modulo 2^N) and is never a range error by itself.
* - base outside {0} union [2, 36]: errno EINVAL, return 0, *endptr set
* to nptr (POSIX.1-2008).
*/
long
strtol(const char *restrict nptr, char **restrict endptr, int base)
{
int neg;
int over;
unsigned long long mag;
int rc = __strtox_scan(nptr, endptr, base, &neg, &mag, &over);
if (rc != STRTOX_OK)
{
if (rc == STRTOX_BADBASE)
{
errno = EINVAL;
}
return 0;
}
if (over != 0 || mag > (unsigned long long)LONG_MAX + (unsigned long long)(neg != 0))
{
errno = ERANGE;
return neg != 0 ? LONG_MIN : LONG_MAX;
}
return neg != 0 ? (long)(0ULL - mag) : (long)mag;
}
unsigned long
strtoul(const char *restrict nptr, char **restrict endptr, int base)
{
int neg;
int over;
unsigned long long mag;
int rc = __strtox_scan(nptr, endptr, base, &neg, &mag, &over);
if (rc != STRTOX_OK)
{
if (rc == STRTOX_BADBASE)
{
errno = EINVAL;
}
return 0;
}
if (over != 0 || mag > (unsigned long long)ULONG_MAX)
{
errno = ERANGE;
return ULONG_MAX;
}
return neg != 0 ? (unsigned long)(0ULL - mag) : (unsigned long)mag;
}
long long
strtoll(const char *restrict nptr, char **restrict endptr, int base)
{
int neg;
int over;
unsigned long long mag;
int rc = __strtox_scan(nptr, endptr, base, &neg, &mag, &over);
if (rc != STRTOX_OK)
{
if (rc == STRTOX_BADBASE)
{
errno = EINVAL;
}
return 0;
}
if (over != 0 || mag > (unsigned long long)LLONG_MAX + (unsigned long long)(neg != 0))
{
errno = ERANGE;
return neg != 0 ? LLONG_MIN : LLONG_MAX;
}
return neg != 0 ? (long long)(0ULL - mag) : (long long)mag;
}
unsigned long long
strtoull(const char *restrict nptr, char **restrict endptr, int base)
{
int neg;
int over;
unsigned long long mag;
int rc = __strtox_scan(nptr, endptr, base, &neg, &mag, &over);
if (rc != STRTOX_OK)
{
if (rc == STRTOX_BADBASE)
{
errno = EINVAL;
}
return 0;
}
if (over != 0 || mag > ULLONG_MAX)
{
errno = ERANGE;
return ULLONG_MAX;
}
return neg != 0 ? 0ULL - mag : mag;
}
/*
* atoi/atol/atoll: strto* with no endptr and base 10 (C23 7.24.1.2). The
* result truncation is that of the target type; out-of-range input is
* undefined behavior, so no additional checking is performed.
*/
int
atoi(const char *nptr)
{
return (int)strtol(nptr, NULL, 10);
}
long
atol(const char *nptr)
{
return strtol(nptr, NULL, 10);
}
long long
atoll(const char *nptr)
{
return strtoll(nptr, NULL, 10);
}