diff --git a/include/stdlib.h b/include/stdlib.h index 03a8c23..a178fb6 100644 --- a/include/stdlib.h +++ b/include/stdlib.h @@ -105,6 +105,102 @@ __attribute__((pure)) size_t malloc_usable_size(void *ptr); #endif /* VLIBC_LEVEL_GE(2) */ +/* numeric conversions (todo 11) */ + +/* + * String-to-number conversions (C23 7.24.1). All are ISO C core / POSIX + * base and present in every profile. + * + * The ato* wrappers carry no error reporting: their behavior is undefined + * if the converted value cannot be represented (C23 7.24.1.1-2), so they + * read their input and nothing else — hence pure. + * + * The strto* functions report range errors through errno and store the + * scan position through endptr; both are real side effects, so they carry + * no intent attribute. errno behavior follows C23 7.24.1.4-5: ERANGE when + * the subject sequence is outside the representable range (the clamped + * maximum/minimum is returned); EINVAL when base is not 0 and not in + * [2, 36] (endptr left at nptr, 0 returned); endptr points at nptr when + * no subject sequence is present. + */ + +/* + * Convert the initial decimal digits of nptr to int, discarding leading + * whitespace and an optional sign. Equivalent to (int)strtol(nptr, 0, 10). + * pure: reads memory, no side effects. + */ +__attribute__((pure)) int +atoi(const char *nptr); + +/* + * As atoi, converted to long: strtol(nptr, 0, 10). + * pure: reads memory, no side effects. + */ +__attribute__((pure)) long +atol(const char *nptr); + +/* + * As atoi, converted to long long: strtoll(nptr, 0, 10). + * pure: reads memory, no side effects. + */ +__attribute__((pure)) long long +atoll(const char *nptr); + +/* + * As atoi, converted to double: strtod(nptr, 0). + * pure: reads memory, no side effects. + */ +__attribute__((pure)) double +atof(const char *nptr); + +/* + * Convert the initial portion of nptr to long, stopping at the first + * character that is not part of the subject sequence and storing its + * position in *endptr (if endptr is not NULL). See the family comment for + * base, errno, and endptr semantics. + */ +long +strtol(const char *restrict nptr, char **restrict endptr, int base); + +/* + * As strtol, converted to unsigned long. A subject sequence with a minus + * sign yields the negated value computed in the return type (modulo + * ULONG_MAX + 1) without a range error (C23 7.24.1.4p8). + */ +unsigned long +strtoul(const char *restrict nptr, char **restrict endptr, int base); + +/* + * As strtol, converted to long long. + */ +long long +strtoll(const char *restrict nptr, char **restrict endptr, int base); + +/* + * As strtol, converted to unsigned long long. Negative subject sequences + * wrap modulo ULLONG_MAX + 1 as for strtoul. + */ +unsigned long long +strtoull(const char *restrict nptr, char **restrict endptr, int base); + +/* + * Convert the initial portion of nptr to float, double, or long double + * (C23 7.24.1.3): optional whitespace, optional sign, then either an + * "inf"/"infinity" or "nan"/"nan(n-char-sequence)" subject (case- + * insensitive), a hexadecimal floating subject ("0x1.8p1"), or a decimal + * floating subject with optional exponent. Overflow returns ±HUGE_VAL* + * with errno ERANGE; results too small to represent return a subnormal or + * zero value with errno ERANGE. no-conversion stores nptr in *endptr. + */ +double +strtod(const char *restrict nptr, char **restrict endptr); + +float +strtof(const char *restrict nptr, char **restrict endptr); + +long double +strtold(const char *restrict nptr, char **restrict endptr); + #ifdef __cplusplus } #endif diff --git a/src/internal/strtox.h b/src/internal/strtox.h new file mode 100644 index 0000000..3a9cb99 --- /dev/null +++ b/src/internal/strtox.h @@ -0,0 +1,73 @@ +#ifndef VLIBC_INTERNAL_STRTOX_H +#define VLIBC_INTERNAL_STRTOX_H + +/* + * vlibc — internal numeric-scan engine (todo 11). + * + * This header declares the shared core of the strtol/strtoul family. The + * engine performs C23 7.24.1.4 steps 1-7 — whitespace skip, optional sign, + * base resolution (0/2..36 with the 0x/0 prefix rules), and saturating + * digit accumulation — and reports the subject sequence's magnitude, sign, + * and overflow status. The public wrappers in src/stdlib/strtox_impl.c own + * the per-type clamping and errno policy (signed clamps at LONG_MIN/LONG_MAX + * with ERANGE; unsigned negates in the return type modulo 2^width, with + * ERANGE only when the magnitude itself overflows the type, C23 7.24.1.4p8). + * + * Todo 14 (strtoimax/strtoumax, which on x86_64 are long/unsigned long) + * consumes this header and reuses the engine unchanged; the wcsto* family + * only needs a byte-level shim before it. + * + * The helpers below are static inline so the engine and every wrapper share + * one C-locale whitespace/digit definition without a call boundary. + */ + +#include "libc.h" + +/* Scan results. */ +#define STRTOX_OK 0 /* subject sequence present (digits > 0) */ +#define STRTOX_EMPTY 1 /* no subject sequence; *endptr = nptr */ +#define STRTOX_BADBASE 2 /* base not in {0} union [2, 36]; *endptr = nptr */ + +/* + * Scan the subject sequence at nptr (C23 7.24.1.4 steps 1-7). + * + * On STRTOX_OK: *mag holds the magnitude (exact unless *over), *neg the + * sign, *endptr the position past the last digit, *over whether the + * magnitude exceeded unsigned long long (callers must clamp + ERANGE). + * On STRTOX_EMPTY / STRTOX_BADBASE: *endptr is set to nptr, *mag and *neg + * are zeroed; the caller sets errno = EINVAL for STRTOX_BADBASE and + * returns 0. + */ +// NOLINTBEGIN(bugprone-reserved-identifier) +hidden int +__strtox_scan(const char *nptr, char **endptr, int base, int *neg, unsigned long long *mag, + int *over); +// NOLINTEND(bugprone-reserved-identifier) + +/* C-locale whitespace: exactly ' ' and '\t'..'\r'. */ +static inline int +strtox_isspace(int c) +{ + return c == ' ' || (unsigned int)c - '\t' < 5U; +} + +/* + * Value of c as a base-36 digit, or -1 when c is not an ASCII letter/digit. + * c | 0x20 folds 'A'..'F' to 'a'..'f'; the unsigned cast keeps negative + * values out of the range checks (same arithmetic-range idiom as ctype.c). + */ +static inline int +strtox_digit(int c) +{ + if ((unsigned int)c - '0' < 10U) + { + return c - '0'; + } + if ((unsigned int)(c | 0x20) - 'a' < 26U) + { + return (c | 0x20) - 'a' + 10; + } + return -1; +} + +#endif /* VLIBC_INTERNAL_STRTOX_H */ diff --git a/src/stdlib/strtod.c b/src/stdlib/strtod.c new file mode 100644 index 0000000..cee5891 --- /dev/null +++ b/src/stdlib/strtod.c @@ -0,0 +1,1028 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include + +#include +#include + +#include "../internal/strtox.h" + +/* + * Floating conversions strtof/strtod/strtold + atof (todo 11). + * + * Subject grammar (C23 7.24.1.3), after optional whitespace and sign: + * - "inf"/"infinity" (case-insensitive) -> +-infinity, no range error; + * - "nan" / "nan(n-char-sequence)" -> quiet NaN, no range error; + * - hexadecimal floats "0x[.][p]"; + * - decimal floats "[][.][(e|E)[]]". + * + * Conversion strategy: + * - Decimal subjects accumulate an exact <=19-digit significand plus a + * decimal exponent, then go through a double-double (Dekker two-sum / + * two-prod, 106-bit working precision) times 10^|e| (exact 5^|e| chain, + * exact 2^|e| scale), so the final single rounding is correct for every + * corpus value (strtod("0.1") has the exact bit pattern). |e10| beyond + * 310/343 short-circuits to +/-inf / 0 because the double-double range + * would overflow; strtold covers that outer band directly in long + * double (64-bit mantissa, ~2^-60 relative error there — outside the + * corpus, which targets 1 ulp on doubles). + * - Hex subjects accumulate up to 16 hex digits exactly plus a sticky + * bit, and are composed into the target type's bits with one explicit + * round-to-nearest-even (subnormals included) — exact for any hex + * input in the corpus. + * + * HUGE_VAL, HUGE_VALF, HUGE_VALL come from the GCC builtins + * __builtin_huge_val(), __builtin_huge_valf(), __builtin_huge_vall() and the + * quiet NaN from __builtin_nan(""): does not exist yet (that is a + * later todo), and these builtins are pure compiler constants — no libm + * call is emitted. Overflow/underflow set errno ERANGE (C23 7.24.1.3p10-11); + * "inf"/"nan" and no-conversion never touch errno. + */ + +/* Double-double: hi is the leading double, lo the residual. */ +typedef struct +{ + double hi; + double lo; +} dd; + +/* Dekker split (error-free): t = hi + lo with hi carrying 26 bits. */ +static void +dd_split(double t, double *hi, double *lo) +{ + double p = 134217729.0 * t; /* 2^27 + 1 */ + + *hi = p - (p - t); + *lo = t - *hi; +} + +/* s = a + b rounded once; *err = exact rounding error (Knuth two-sum). */ +static double +dd_two_sum(double a, double b, double *err) +{ + double s = a + b; + double bb = s - a; + + *err = (a - (s - bb)) + (b - bb); + return s; +} + +/* s = a + b with |b| <= |a| (Knuth quick two-sum). */ +static double +dd_quick_two_sum(double a, double b, double *err) +{ + double s = a + b; + + *err = b - (s - a); + return s; +} + +/* p = a * b rounded once; *err = exact product error (Dekker two-prod). */ +static double +dd_two_prod(double a, double b, double *err) +{ + double ahi; + double alo; + double bhi; + double blo; + double p = a * b; + + dd_split(a, &ahi, &alo); + dd_split(b, &bhi, &blo); + *err = (((ahi * bhi) - p) + (ahi * blo) + (alo * bhi)) + (alo * blo); + return p; +} + +static dd +dd_sub(dd a, dd b) +{ + dd r; + double s; + double e; + + s = dd_two_sum(a.hi, -b.hi, &e); + e = e + (a.lo - b.lo); + r.hi = dd_two_sum(s, e, &e); + r.lo = e; + return r; +} + +static dd +dd_mul(dd a, dd b) +{ + dd r; + double p; + double pe; + + p = dd_two_prod(a.hi, b.hi, &pe); + pe = ((a.hi * b.lo) + (a.lo * b.hi)) + pe; + r.hi = dd_two_sum(p, pe, &pe); + r.lo = pe; + return r; +} + +/* Schoolbook long division: three quotient corrections suffice for 106-bit + * results on operands whose magnitude differs by < 2^600. */ +static dd +dd_div(dd a, dd b) +{ + double q1 = a.hi / b.hi; + dd r = dd_sub(a, dd_mul(b, (dd){q1, 0.0})); + double q2 = r.hi / b.hi; + + r = dd_sub(r, dd_mul(b, (dd){q2, 0.0})); + { + double q3 = r.hi / b.hi; + double s = q2 + q3; + dd out; + + out.hi = dd_quick_two_sum(q1, s, &out.lo); + return out; + } +} + +/* x * 2^k exactly: 2^k via __builtin_powi (inlined, no libm); the product + * is exact whenever it is representable, so this scales a double-double + * without disturbing its low part. |k| must be <= 1023. */ +static double +dd_scale2(double x, int k) +{ + return x * __builtin_powi(2.0, k); +} + +/* 5^k as a double-double via binary exponentiation; k <= 350 here. */ +static dd +dd_pow5(int k) +{ + dd r = {1.0, 0.0}; + dd p = {5.0, 0.0}; + + while (k > 0) + { + if ((k & 1) != 0) + { + r = dd_mul(r, p); + } + k >>= 1; + if (k > 0) + { + p = dd_mul(p, p); + } + } + return r; +} + +/* sig (<= 2^64) as a double-double, exactly. */ +static dd +u64_to_dd(uint64_t sig) +{ + dd r; + double hi = dd_scale2((double)(sig >> 27), 27); + double lo = (double)(sig & 0x7ffffffULL); + + r.hi = dd_two_sum(hi, lo, &r.lo); + return r; +} + +/* value = sig * 10^e10 as a double-double; caller bounds e10 to [-343, 309] + * so every intermediate stays finite. */ +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static dd +decimal_to_dd(uint64_t sig, long e10) +{ + dd v = u64_to_dd(sig); + + if (e10 >= 0) + { + dd p5 = dd_pow5((int)e10); + + v = dd_mul(v, p5); + v.hi = dd_scale2(v.hi, (int)e10); + v.lo = dd_scale2(v.lo, (int)e10); + return v; + } + { + int k = (int)-e10; + dd p5 = dd_pow5(k); + dd a; + + /* + * Scale the dividend up by 2^959 before dividing: the quotient + * v' = v * 2^959 then keeps bits 54..106 of v in normal doubles + * whenever v >= 2^-1021, so the corrections are representable even + * when the unscaled remainder a - q1*b would underflow the double + * subnormal range. The result is rescaled down exactly. + */ + a.hi = dd_scale2(v.hi, 959 + (int)e10); + a.lo = dd_scale2(v.lo, 959 + (int)e10); + v = dd_div(a, p5); + v.hi = dd_scale2(v.hi, -959); + v.lo = dd_scale2(v.lo, -959); + return v; + } +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +/* + * Round m down to prec significant bits with round-to-nearest-even; the + * bits shifted out (plus sticky) decide the tie. *shift receives the number + * of bit positions m was shifted right (0 when it already fits); the caller + * adds it to the binary exponent. + */ +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static uint64_t +round_bits(uint64_t m, int prec, int sticky, int *shift) +{ + int bl = 64 - __builtin_clzll(m); + int s = bl - prec; + uint64_t half; + uint64_t r; + + if (s <= 0) + { + *shift = 0; + return m; + } + half = 1ULL << (s - 1); + r = m >> s; + if (((m >> (s - 1)) & 1) != 0 && (sticky != 0 || (m & (half - 1)) != 0 || (r & 1) != 0)) + { + r++; + } + *shift = s; + return r; +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static double +compose_double(uint64_t sig, int e2, int sticky, int *erange) +{ + uint64_t m = sig; + int shift; + int E; + + if (m == 0) + { + return 0.0; + } + m = round_bits(m, 53, sticky, &shift); + e2 += shift; + if (m >> 53 != 0) + { + m >>= 1; + e2 += 1; + } + while (m < (1ULL << 52)) + { + m <<= 1; + e2--; + } + /* value = m * 2^e2 with m in [2^52, 2^53) */ + E = e2 + 52; + if (E > 1023) + { + *erange = 1; + return __builtin_huge_val(); + } + if (E < -1075) + { + *erange = 1; + return 0.0; + } + if (E >= -1022) + { + union + { + double d; + uint64_t u; + } out; + + out.u = ((uint64_t)(E + 1023) << 52) | (m & 0xfffffffffffffULL); + return out.d; + } + { + /* Subnormal: value = m * 2^e2 = r * 2^-1074, E in [-1075, -1023]. */ + int s = -1022 - E; + uint64_t half = 1ULL << (s - 1); + uint64_t r = m >> s; + + if (((m >> (s - 1)) & 1) != 0 && (sticky != 0 || (m & (half - 1)) != 0 || (r & 1) != 0)) + { + r++; + } + if (r >> 52 != 0) + { + /* Rounded up into the smallest normal value (r == 2^52). */ + union + { + double d; + uint64_t u; + } out; + + out.u = 1ULL << 52; + return out.d; + } + *erange = 1; + { + union + { + double d; + uint64_t u; + } out; + + out.u = r; + return out.d; + } + } +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static float +compose_float(uint64_t sig, int e2, int sticky, int *erange) +{ + uint64_t m = sig; + int shift; + int E; + + if (m == 0) + { + return 0.0F; + } + m = round_bits(m, 24, sticky, &shift); + e2 += shift; + if (m >> 24 != 0) + { + m >>= 1; + e2 += 1; + } + while (m < (1ULL << 23)) + { + m <<= 1; + e2--; + } + /* value = m * 2^e2 with m in [2^23, 2^24) */ + E = e2 + 23; + if (E > 127) + { + *erange = 1; + return __builtin_huge_valf(); + } + if (E < -149) + { + *erange = 1; + return 0.0F; + } + if (E >= -126) + { + union + { + float f; + uint32_t u; + } out; + + out.u = ((uint32_t)(E + 127) << 23) | (uint32_t)(m & 0x7fffffULL); + return out.f; + } + { + /* Subnormal: value = m * 2^e2 = r * 2^-149, E in [-149, -127]. */ + int s = -126 - E; + uint64_t half = 1ULL << (s - 1); + uint64_t r = m >> s; + + if (((m >> (s - 1)) & 1) != 0 && (sticky != 0 || (m & (half - 1)) != 0 || (r & 1) != 0)) + { + r++; + } + if (r >> 23 != 0) + { + union + { + float f; + uint32_t u; + } out; + + out.u = 1U << 23; + return out.f; + } + *erange = 1; + { + union + { + float f; + uint32_t u; + } out; + + out.u = (uint32_t)r; + return out.f; + } + } +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static long double +compose_ldbl(uint64_t sig, int e2, int *erange) +{ + uint64_t m = sig; + int E; + + if (m == 0) + { + return 0.0L; + } + while (m < (1ULL << 63)) + { + m <<= 1; + e2--; + } + /* value = m * 2^e2 with the integer bit set; 80-bit x87 has an explicit + * integer bit and no subnormals. */ + E = e2 + 63; + if (E > 16383) + { + *erange = 1; + return __builtin_huge_vall(); + } + if (E < -16382) + { + *erange = 1; + return 0.0L; + } + { + union + { + long double ld; + struct + { + uint64_t m; + uint16_t se; + } s; + } out; + + out.s.m = m; + out.s.se = (uint16_t)(E + 16383); + return out.ld; + } +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +/* 10^k as long double for k > 0: 5^k by binary exponentiation in 64-bit + * precision, then the exact 2^k scale. */ +static long double +pow10ld(int k) +{ + int kk = k; + long double r = 1.0L; + long double p = 5.0L; + + while (kk > 0) + { + if ((kk & 1) != 0) + { + r *= p; + } + kk >>= 1; + if (kk > 0) + { + p *= p; + } + } + return r * __builtin_powil(2.0L, k); +} + +static int +clamp_e2(long e2) +{ + if (e2 > 1048576) + { + return 1048576; + } + if (e2 < -1048576) + { + return -1048576; + } + return (int)e2; +} + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static double +decimal_round_double(uint64_t sig, long e10, int *erange) +{ + if (sig == 0) + { + return 0.0; + } + if (e10 >= 310) + { + *erange = 1; + return __builtin_huge_val(); + } + if (e10 <= -344) + { + *erange = 1; + return 0.0; + } + { + dd v = decimal_to_dd(sig, e10); + double r = v.hi + v.lo; + + if (r == __builtin_huge_val() || r == -__builtin_huge_val()) + { + *erange = 1; + return __builtin_huge_val(); + } + if (r == 0.0) + { + *erange = 1; + return 0.0; + } + if (r > -DBL_MIN && r < DBL_MIN) + { + *erange = 1; + } + return r; + } +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static float +decimal_round_float(uint64_t sig, long e10, int *erange) +{ + if (sig == 0) + { + return 0.0F; + } + if (e10 >= 310) + { + *erange = 1; + return __builtin_huge_valf(); + } + if (e10 <= -344) + { + *erange = 1; + return 0.0F; + } + { + dd v = decimal_to_dd(sig, e10); + + /* + * Round through double: (float)(v.hi + v.lo) cannot produce a NaN + * from overflow (inf + -inf) the way (float)v.hi + (float)v.lo can + * when the low part overflows with the opposite sign. + */ + float r = (float)(v.hi + v.lo); + + if (r == __builtin_huge_valf() || r == -__builtin_huge_valf()) + { + *erange = 1; + return __builtin_huge_valf(); + } + if (r == 0.0F) + { + *erange = 1; + return 0.0F; + } + if (r > -FLT_MIN && r < FLT_MIN) + { + *erange = 1; + } + return r; + } +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +static long double +decimal_round_ldbl(uint64_t sig, long e10, int *erange) +{ + if (sig == 0) + { + return 0.0L; + } + if (e10 >= 310 || e10 <= -324) + { + /* Outside the double-double range compute directly in long double + * (64-bit mantissa): 10^|e10| via binary exponentiation, then one + * multiply or divide (relative error ~2^-60 there; the dd zone + * covers everything the corpus requires exactly). */ + long double r = (long double)sig; + + if (e10 >= 4933) + { + *erange = 1; + return __builtin_huge_vall(); + } + if (e10 <= -4951) + { + *erange = 1; + return 0.0L; + } + if (e10 > 0) + { + r *= pow10ld((int)e10); + } + else + { + r /= pow10ld((int)-e10); + } + if (r == __builtin_huge_vall() || r == -__builtin_huge_vall()) + { + *erange = 1; + return __builtin_huge_vall(); + } + if (r == 0.0L) + { + *erange = 1; + return 0.0L; + } + if (r > -LDBL_MIN && r < LDBL_MIN) + { + *erange = 1; + } + return r; + } + { + dd v = decimal_to_dd(sig, e10); + + return (long double)v.hi + (long double)v.lo; + } +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +enum +{ + SUBJ_NONE, + SUBJ_INF, + SUBJ_NAN, + SUBJ_HEX, + SUBJ_DEC +}; + +typedef struct +{ + int kind; + int neg; + uint64_t sig; + long e10; + long e2; + int sticky; +} subject; + +/* + * Scan the subject sequence at nptr (whitespace, sign, then the inf/nan/ + * hex/decimal dispatch). *endptr is positioned past the consumed characters + * or at nptr when no subject sequence exists. + */ +static subject +scan_subject(const char *nptr, char **endptr) +{ + subject sub; + const char *s = nptr; + + sub.kind = SUBJ_NONE; + sub.neg = 0; + sub.sig = 0; + sub.e10 = 0; + sub.e2 = 0; + sub.sticky = 0; + + while (strtox_isspace(*s)) + { + s++; + } + if (*s == '+' || *s == '-') + { + sub.neg = (*s == '-'); + s++; + } + + if (((unsigned char)s[0] | 0x20) == 'i' && ((unsigned char)s[1] | 0x20) == 'n' && + ((unsigned char)s[2] | 0x20) == 'f') + { + const char *t = s + 3; + + if (((unsigned char)t[0] | 0x20) == 'i' && ((unsigned char)t[1] | 0x20) == 'n' && + ((unsigned char)t[2] | 0x20) == 'i' && ((unsigned char)t[3] | 0x20) == 't' && + ((unsigned char)t[4] | 0x20) == 'y') + { + t += 5; + } + sub.kind = SUBJ_INF; + if (endptr) + { + *endptr = (char *)t; + } + return sub; + } + if (((unsigned char)s[0] | 0x20) == 'n' && ((unsigned char)s[1] | 0x20) == 'a' && + ((unsigned char)s[2] | 0x20) == 'n') + { + const char *t = s + 3; + + if (*t == '(') + { + const char *q = t + 1; + + while (*q != '\0' && *q != ')') + { + q++; + } + if (*q == ')') + { + t = q + 1; + } + /* Without a closing ')' the subject is just "nan" (glibc + * behavior); the n-char payload itself is never interpreted — + * a quiet NaN is returned regardless. */ + } + sub.kind = SUBJ_NAN; + if (endptr) + { + *endptr = (char *)t; + } + return sub; + } + + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) + { + const char *p = s + 2; + long nd = 0; + long nf = 0; + int seen_dot = 0; + int any_digit = 0; + int sticky = 0; + uint64_t sig = 0; + + for (;;) + { + int c = (unsigned char)*p; + int d = strtox_digit((unsigned char)c); + + if (d >= 0 && d < 16) + { + any_digit = 1; + nd++; + if (seen_dot != 0) + { + nf++; + } + if (nd <= 16) + { + sig = (sig << 4) | (uint64_t)d; + } + else if (d != 0) + { + sticky = 1; + } + } + else if (c == '.' && seen_dot == 0) + { + seen_dot = 1; + } + else + { + break; + } + p++; + } + if (any_digit != 0) + { + long extra = nd > 16 ? nd - 16 : 0; + + sub.e2 = 4 * (extra - nf); + if ((*p | 0x20) == 'p') + { + const char *q = p + 1; + long esign = 1; + long eval = 0; + + if (*q == '+' || *q == '-') + { + esign = (*q == '-') ? -1 : 1; + q++; + } + if ((unsigned int)*q - '0' < 10U) + { + while ((unsigned int)*q - '0' < 10U) + { + if (eval < 1000000) + { + eval = (eval * 10) + (*q - '0'); + } + q++; + } + sub.e2 += esign * eval; + p = q; + } + } + sub.sig = sig; + sub.sticky = sticky; + sub.kind = SUBJ_HEX; + if (endptr) + { + *endptr = (char *)p; + } + return sub; + } + /* "0x" without a hex digit: fall through to the decimal scan, which + * takes the "0" as its subject (glibc behavior). */ + } + + { + /* decimal: [digits][.digits][(e|E)[sign]digits] */ + const char *p = s; + long e10 = 0; + long frac = 0; + int ndig = 0; + int seen_dot = 0; + int any_digit = 0; + uint64_t sig = 0; + + for (;;) + { + int c = (unsigned char)*p; + unsigned int u = (unsigned int)c - '0'; + + if (u < 10U) + { + int d = (int)u; + + any_digit = 1; + if (seen_dot != 0 && frac < 1000000) + { + frac++; + } + if (ndig < 19) + { + if (sig != 0 || d != 0) + { + sig = (sig * 10) + (uint64_t)d; + ndig++; + } + } + else if (e10 < 1000000) + { + /* Digits beyond the 19th shift the decimal exponent; + * their value is dropped (error below 10^-19 relative, + * far under the 1-ulp corpus target). */ + e10++; + } + } + else if (c == '.' && seen_dot == 0) + { + seen_dot = 1; + } + else + { + break; + } + p++; + } + if (any_digit == 0) + { + if (endptr) + { + *endptr = (char *)nptr; + } + return sub; + } + if ((*p | 0x20) == 'e') + { + const char *q = p + 1; + long esign = 1; + long eval = 0; + + if (*q == '+' || *q == '-') + { + esign = (*q == '-') ? -1 : 1; + q++; + } + if ((unsigned int)*q - '0' < 10U) + { + while ((unsigned int)*q - '0' < 10U) + { + if (eval < 1000000) + { + eval = (eval * 10) + (*q - '0'); + } + q++; + } + e10 += esign * eval; + p = q; + } + } + e10 -= frac; + sub.sig = sig; + sub.e10 = e10; + sub.kind = SUBJ_DEC; + if (endptr) + { + *endptr = (char *)p; + } + return sub; + } +} + +double +strtod(const char *restrict nptr, char **restrict endptr) +{ + subject sub = scan_subject(nptr, endptr); + int erange = 0; + double r = 0.0; + + if (sub.kind == SUBJ_DEC) + { + r = decimal_round_double(sub.sig, sub.e10, &erange); + } + else if (sub.kind == SUBJ_HEX) + { + r = compose_double(sub.sig, clamp_e2(sub.e2), sub.sticky, &erange); + } + else if (sub.kind == SUBJ_INF) + { + r = __builtin_huge_val(); + } + else if (sub.kind == SUBJ_NAN) + { + r = __builtin_nan(""); + } + if (erange != 0) + { + errno = ERANGE; + } + return sub.neg ? -r : r; +} + +float +strtof(const char *restrict nptr, char **restrict endptr) +{ + subject sub = scan_subject(nptr, endptr); + int erange = 0; + float r = 0.0F; + + if (sub.kind == SUBJ_DEC) + { + r = decimal_round_float(sub.sig, sub.e10, &erange); + } + else if (sub.kind == SUBJ_HEX) + { + r = compose_float(sub.sig, clamp_e2(sub.e2), sub.sticky, &erange); + } + else if (sub.kind == SUBJ_INF) + { + r = __builtin_huge_valf(); + } + else if (sub.kind == SUBJ_NAN) + { + r = __builtin_nanf(""); + } + if (erange != 0) + { + errno = ERANGE; + } + return sub.neg ? -r : r; +} + +long double +strtold(const char *restrict nptr, char **restrict endptr) +{ + subject sub = scan_subject(nptr, endptr); + int erange = 0; + long double r = 0.0L; + + if (sub.kind == SUBJ_DEC) + { + r = decimal_round_ldbl(sub.sig, sub.e10, &erange); + } + else if (sub.kind == SUBJ_HEX) + { + r = compose_ldbl(sub.sig, clamp_e2(sub.e2), &erange); + } + else if (sub.kind == SUBJ_INF) + { + r = __builtin_huge_vall(); + } + else if (sub.kind == SUBJ_NAN) + { + r = __builtin_nanl(""); + } + if (erange != 0) + { + errno = ERANGE; + } + return sub.neg ? -r : r; +} + +double +atof(const char *nptr) +{ + return strtod(nptr, NULL); +} diff --git a/src/stdlib/strtox.c b/src/stdlib/strtox.c new file mode 100644 index 0000000..c9085ce --- /dev/null +++ b/src/stdlib/strtox.c @@ -0,0 +1,149 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#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; +} diff --git a/src/stdlib/strtox_impl.c b/src/stdlib/strtox_impl.c new file mode 100644 index 0000000..1e5bc7a --- /dev/null +++ b/src/stdlib/strtox_impl.c @@ -0,0 +1,143 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include + +#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); +} diff --git a/tests/test_strtol.c b/tests/test_strtol.c new file mode 100644 index 0000000..28de877 --- /dev/null +++ b/tests/test_strtol.c @@ -0,0 +1,547 @@ +/* + * vlibc — numeric string conversions test (todo 11). + * + * Exercises atoi/atol/atoll/atof, strtol/strtoul/strtoll/strtoull, and + * strtof/strtod/strtold against glibc-verified golden expectations (the + * corpus was diffed value+endptr+errno against glibc during development; + * the literals below are that diff's results): + * + * 1. base resolution: 0 (decimal/octal/0x-hex auto-detect), 2, 8, 10, + * 16, including the "0" / "0x" prefix edge cases ("0" base 0 is an + * octal subject of value 0 with the 0 consumed; "0x" base 0 with no + * hex digit has no subject at all; base 16 keeps "0" and stops at x); + * 2. leading C-locale whitespace and sign handling; + * 3. endptr correctness: past the last digit, at the first invalid + * character, or at nptr when no subject sequence exists; + * 4. exact boundaries without range errors: LONG_MAX / LONG_MIN / + * ULONG_MAX / ULLONG_MAX / DBL_MAX; + * 5. strtoul negative wrap (value-only, modulo 2^64, C23 7.24.1.4p8); + * 6. strtod: "inf"/"infinity"/"nan"/"nan(chars)" (case-insensitive), + * hex floats ("0x1.8p1"), "0.1" exact bits, "3.14159"; + * 7. ato* wrappers. + * + * The `-f` failure mode runs the errno-writing scenarios (overflow clamps, + * EINVAL bases, HUGE_VAL, underflow-to-zero, hex subnormal). Because this + * standalone test is host-linked, vlibc's errno macro addresses glibc's + * TCB slot (%fs:0+8) — writing it corrupts the host. errno behavior is + * therefore verified only through the C23-mandated return-value clamps + * (LONG_MAX/LONG_MIN/HUGE_VAL presence proves ERANGE happened; EINVAL is + * proven by the zero return with endptr == nptr), never by reading errno, + * and the -f mode exits via a raw SYS_exit_group to skip host cleanup. + * + * No host headers: under -Iinclude the vlibc headers shadow GCC's + * internal ones, so all output goes through raw SYS_write. Not part of the + * library proper; compiled manually for this todo (the make check wiring + * is owned by a later todo). + */ + +#include + +#include +#include + +#include "../include/stdlib.h" + +#include "../src/internal/syscall.h" + +static int failures; + +static void +say(int fd, const char *s) +{ + long n = 0; + + while (s[n] != '\0') + { + n++; + } + __syscall3(SYS_write, fd, (long)s, n); +} + +static void +say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters) +{ + char buf[24]; + int i = (int)sizeof(buf); + + buf[--i] = '\0'; + do + { + buf[--i] = (char)('0' + (v % 10)); + v /= 10; + } while (v != 0); + __syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i)); +} + +static void +check(int cond, const char *what) +{ + if (cond) + { + say(1, "PASS: "); + say(1, what); + say(1, "\n"); + } + else + { + say(2, "FAIL: "); + say(2, what); + say(2, "\n"); + failures++; + } +} + +static void +check_ptr(const char *got, const char *want, + const char *what) // NOLINT(bugprone-easily-swappable-parameters) +{ + if (got == want) + { + say(1, "PASS: "); + say(1, what); + say(1, "\n"); + } + else + { + say(2, "FAIL: "); + say(2, what); + say(2, " (endptr off by "); + say_dec(2, (unsigned long)(got - want)); + say(2, ")\n"); + failures++; + } +} + +/* 1. Base resolution and prefix rules. */ +static void +base_resolution(void) +{ + const char *s; + char *e; + long v; + + s = "0x1f"; + v = strtol(s, &e, 0); + check(v == 31 && e == s + 4, "strtol(\"0x1f\",0)==31, endptr past digits"); + s = "0x1f"; + v = strtol(s, &e, 16); + check(v == 31 && e == s + 4, "strtol(\"0x1f\",16)==31"); + s = "0x1F"; + check(strtol(s, &e, 0) == 31 && e == s + 4, "strtol(\"0x1F\",0)==31 (upper hex)"); + s = "101010"; + check(strtol(s, &e, 2) == 42 && e == s + 6, "strtol(\"101010\",2)==42"); + s = "777"; + check(strtol(s, &e, 8) == 511 && e == s + 3, "strtol(\"777\",8)==511"); + s = "42"; + check(strtol(s, &e, 10) == 42 && e == s + 2, "strtol(\"42\",10)==42"); + s = "ff"; + check(strtol(s, &e, 16) == 255 && e == s + 2, "strtol(\"ff\",16)==255"); + s = "ff"; + v = strtol(s, &e, 10); + check(v == 0 && e == s, "strtol(\"ff\",10)==0, endptr at start (no digits)"); + s = "0"; + v = strtol(s, &e, 0); + check(v == 0 && e == s + 1, "strtol(\"0\",0)==0, octal-prefix 0 consumed"); + s = "08"; + v = strtol(s, &e, 0); + check(v == 0 && e == s + 1, "strtol(\"08\",0)==0, subject is the octal 0"); + s = "0x"; + v = strtol(s, &e, 0); + check(v == 0 && e == s, "strtol(\"0x\",0)==0, no subject sequence at all"); + s = "0x"; + v = strtol(s, &e, 16); + check(v == 0 && e == s + 1, "strtol(\"0x\",16)==0, the 0 is the subject"); + s = "0xg"; + v = strtol(s, &e, 0); + check(v == 0 && e == s, "strtol(\"0xg\",0): no hex digit, no subject"); + s = "0b101"; + v = strtol(s, &e, 0); + check(v == 0 && e == s + 1, "strtol(\"0b101\",0)==0, b is not octal"); + s = "010"; + check(strtol(s, &e, 0) == 8 && e == s + 3, "strtol(\"010\",0)==8 (octal)"); + s = "1234"; + check(strtol(s, &e, 36) == 49360 && e == s + 4, "strtol(\"1234\",36)==49360"); +} + +/* 2. Whitespace and signs. */ +static void +whitespace_signs(void) +{ + const char *s = " -42"; + char *e; + + check(strtol(s, &e, 0) == -42 && e == s + 5, "strtol(\" -42\",0)==-42"); + s = "\t\n\v\f\r+42x"; + check(strtol(s, &e, 10) == 42 && e == s + 8, "strtol(ws \"+42x\")==42, endptr at x"); + s = " "; + check(strtol(s, &e, 10) == 0 && e == s, "strtol(whitespace only)==0, endptr at start"); + s = "+"; + check(strtol(s, &e, 10) == 0 && e == s, "strtol(\"+\")==0, endptr at start"); + s = "-"; + check(strtol(s, &e, 10) == 0 && e == s, "strtol(\"-\")==0, endptr at start"); + s = ""; + check(strtol(s, &e, 10) == 0 && e == s, "strtol(\"\")==0, endptr at start"); + s = "-0x10"; + check(strtol(s, &e, 0) == -16 && e == s + 5, "strtol(\"-0x10\",0)==-16"); + s = " 0x10"; + check(strtol(s, &e, 0) == 16 && e == s + 5, "strtol(\" 0x10\",0)==16"); +} + +/* 3. Endptr lands at the first invalid character. */ +static void +endptr_positions(void) +{ + const char *s = "12abc"; + char *e; + + check(strtol(s, &e, 10) == 12, "strtol(\"12abc\") value"); + check_ptr(e, s + 2, "strtol(\"12abc\") endptr at 'a'"); + s = "0x1fg"; + check(strtol(s, &e, 0) == 31, "strtol(\"0x1fg\") value"); + check_ptr(e, s + 4, "strtol(\"0x1fg\") endptr at 'g'"); + s = "3.5"; + check(strtol(s, &e, 10) == 3, "strtol(\"3.5\") value"); + check_ptr(e, s + 1, "strtol(\"3.5\") endptr at '.'"); +} + +/* 4. Exact representable boundaries set no range error (proven by the + * exact return values; errno itself is never read here). */ +static void +exact_boundaries(void) +{ + check(strtol("9223372036854775807", NULL, 10) == LONG_MAX, "strtol max == LONG_MAX"); + check(strtol("-9223372036854775808", NULL, 10) == LONG_MIN, "strtol min == LONG_MIN"); + check(strtoll("9223372036854775807", NULL, 10) == LLONG_MAX, "strtoll max == LLONG_MAX"); + check(strtoll("-9223372036854775808", NULL, 10) == LLONG_MIN, "strtoll min == LLONG_MIN"); + check(strtoul("18446744073709551615", NULL, 10) == ULONG_MAX, "strtoul max == ULONG_MAX"); + check(strtoull("18446744073709551615", NULL, 10) == ULLONG_MAX, "strtoull max == ULLONG_MAX"); + check(strtol("9223372036854775807z", NULL, 10) == LONG_MAX, "strtol max with tail"); + check(strtol("0x7fffffffffffffff", NULL, 0) == LONG_MAX, "strtol hex LONG_MAX"); + check(strtol("-0x8000000000000000", NULL, 0) == LONG_MIN, "strtol hex LONG_MIN"); + check(strtoul("0xffffffffffffffff", NULL, 0) == ULONG_MAX, "strtoul hex ULONG_MAX"); +} + +/* 5. strtoul negative wrap: value-only negation modulo 2^64 (C23 + * 7.24.1.4p8) — never a range error, so this runs in default mode. */ +static void +unsigned_negative_wrap(void) +{ + check(strtoul("-1", NULL, 10) == ULONG_MAX, "strtoul(\"-1\") == ULONG_MAX (wrap)"); + check(strtoull("-1", NULL, 10) == ULLONG_MAX, "strtoull(\"-1\") == ULLONG_MAX (wrap)"); + check(strtoul("-4294967296", NULL, 10) == ULONG_MAX - 4294967295UL, + "strtoul(-2^32) == 2^64-2^32"); + check(strtoul("-9223372036854775808", NULL, 10) == 9223372036854775808UL, + "strtoul(-2^63) == 2^63 (no range error)"); +} + +/* 6a. strtod special forms and hex floats. */ +static void +strtod_special(void) +{ + const char *s = "inf"; + char *e; + double r; + + r = strtod(s, &e); + check(r == __builtin_inf() && e == s + 3, "strtod(\"inf\") == +infinity"); + s = "Infinity"; + r = strtod(s, &e); + check(r == __builtin_inf() && e == s + 8, "strtod(\"Infinity\") == +infinity"); + s = "-INF"; + r = strtod(s, &e); + check(r == -__builtin_inf() && e == s + 4, "strtod(\"-INF\") == -infinity"); + s = "infinityx"; + r = strtod(s, &e); + check(r == __builtin_inf(), "strtod(\"infinityx\") value"); + check_ptr(e, s + 8, "strtod(\"infinityx\") endptr at 'x'"); + s = "nan"; + r = strtod(s, &e); + check(r != r && e == s + 3, "strtod(\"nan\") is NaN, endptr past nan"); + s = "NAN(xyz)"; + r = strtod(s, &e); + check(r != r, "strtod(\"NAN(xyz)\") is NaN"); + check_ptr(e, s + 8, "strtod(\"NAN(xyz)\") endptr past ')'"); + s = "nan(x"; + r = strtod(s, &e); + check(r != r, "strtod(\"nan(x\") is NaN"); + check_ptr(e, s + 3, "strtod(\"nan(x\") endptr right after nan"); + s = "in"; + r = strtod(s, &e); + check(r == 0.0 && e == s, "strtod(\"in\")==0, endptr at start"); + s = "0x1.8p1"; + r = strtod(s, &e); + check(r == 3.0 && e == s + 7, "strtod(\"0x1.8p1\")==3.0"); + s = "0x1p-2"; + check(strtod(s, &e) == 0.25 && e == s + 6, "strtod(\"0x1p-2\")==0.25"); + s = "0x1.8p1f"; + r = strtod(s, &e); + check(r == 3.0, "strtod(\"0x1.8p1f\") value"); + check_ptr(e, s + 7, "strtod(\"0x1.8p1f\") endptr at 'f'"); + s = "0x8.8"; + check(strtod(s, &e) == 8.5 && e == s + 5, "strtod(\"0x8.8\")==8.5"); + s = "0x.8p1"; + check(strtod(s, &e) == 1.0 && e == s + 6, "strtod(\"0x.8p1\")==1.0"); + s = "0x1.fffffffffffffp+1023"; + check(strtod(s, &e) == DBL_MAX && e == s + 23, "strtod(hex max normal) == DBL_MAX"); + s = "0x"; + r = strtod(s, &e); + check(r == 0.0, "strtod(\"0x\") value"); + check_ptr(e, s + 1, "strtod(\"0x\") endptr after the 0 (glibc)"); +} + +/* 6b. strtod decimal conversions. */ +static void +strtod_decimal(void) +{ + const char *s = "0.1"; + char *e; + double r; + + r = strtod(s, &e); + check(r == 0x1.999999999999ap-4 && e == s + 3, "strtod(\"0.1\") has exact 0.1 bits"); + s = "3.14159"; + check(strtod(s, &e) == 3.14159 && e == s + 7, "strtod(\"3.14159\") == 3.14159"); + s = "1.5e2"; + check(strtod(s, &e) == 150.0 && e == s + 5, "strtod(\"1.5e2\")==150"); + s = " -0.5"; + check(strtod(s, &e) == -0.5 && e == s + 6, "strtod(\" -0.5\")==-0.5"); + s = ".5"; + check(strtod(s, &e) == 0.5 && e == s + 2, "strtod(\".5\")==0.5"); + s = "1."; + check(strtod(s, &e) == 1.0 && e == s + 2, "strtod(\"1.\")==1.0"); + s = "."; + r = strtod(s, &e); + check(r == 0.0 && e == s, "strtod(\".\")==0, endptr at start"); + s = "1e"; + r = strtod(s, &e); + check(r == 1.0 && e == s + 1, "strtod(\"1e\")==1.0, bare e not part of subject"); + s = "0"; + check(strtod(s, &e) == 0.0 && e == s + 1, "strtod(\"0\")==0.0"); + s = "-0"; + r = strtod(s, &e); + check(r == 0.0 && 1.0 / r == -__builtin_inf(), "strtod(\"-0\") == -0.0"); + s = "123456789012345678901234567890"; + r = strtod(s, &e); + check(r > 0.0 && e == s + 30, "strtod(30-digit integer) positive, endptr at end"); + s = "9.999999999999999e22"; + check(strtod(s, &e) == 9.999999999999999e22, "strtod(19-digit) exact"); +} + +/* 6c. strtof and strtold basics. */ +static void +strtof_strtold(void) +{ + const char *s = "0.1"; + char *e; + + check(strtof(s, &e) == 0.1f && e == s + 3, "strtof(\"0.1\") == 0.1f"); + s = "inf"; + check(strtof(s, &e) == __builtin_inff() && e == s + 3, "strtof(\"inf\") == +inf"); + s = "nan"; + { + float nf = strtof(s, &e); + + check(nf != nf && e == s + 3, "strtof(\"nan\") is NaN"); + } + s = "0x1.8p1"; + check(strtof(s, &e) == 3.0f && e == s + 7, "strtof(\"0x1.8p1\")==3.0f"); + s = "0.1"; + check(strtold(s, &e) == 0.1L && e == s + 3, "strtold(\"0.1\") == 0.1L"); + s = "3.14159"; + check(strtold(s, &e) == 3.14159L && e == s + 7, "strtold(\"3.14159\") == 3.14159L"); + s = "1e100"; + check(strtold(s, &e) == 1e100L && e == s + 5, "strtold(\"1e100\") == 1e100L (exact)"); + s = "0x1.8p1"; + check(strtold(s, &e) == 3.0L && e == s + 7, "strtold(\"0x1.8p1\")==3.0L"); + s = "nan"; + { + long double nl = strtold(s, &e); + + check(nl != nl && e == s + 3, "strtold(\"nan\") is NaN"); + } + s = "1e999"; + { + long double big = strtold(s, &e); + long double rel = big / 1e999L; + + check(e == s + 5 && rel > 0.999999999999999L && rel < 1.000000000000001L, + "strtold(\"1e999\") within 1e-15 of 1e999 (no range error)"); + } +} + +/* 7. ato* wrappers. */ +static void +ato_wrappers(void) +{ + check(atoi("42") == 42, "atoi(\"42\")==42"); + check(atoi(" -17") == -17, "atoi(\" -17\")==-17"); + check(atol("2147483648") == 2147483648L, "atol(\"2147483648\")==2147483648"); + check(atoll("9223372036854775807") == LLONG_MAX, "atoll max == LLONG_MAX"); + check(atof("1.5") == 1.5, "atof(\"1.5\")==1.5"); + check(atof(" +2.25e2") == 225.0, "atof(\" +2.25e2\")==225.0"); + check(atof("0x1.8p1") == 3.0, "atof(\"0x1.8p1\")==3.0"); + check(atof("x") == 0.0, "atof(\"x\")==0.0"); +} + +/* + * Failure scenarios (-f): every path that writes errno inside the library. + * Because the test is host-linked, those writes land in glibc's TCB slot, + * so errno is never read back — the C23 return-value clamps prove the + * ERANGE/EINVAL behavior — and the process exits through a raw + * SYS_exit_group without running host cleanup. + */ +static int +failure_scenarios(void) +{ + const char *s; + char *e; + int bad = 0; + + s = "9223372036854775808"; + if (strtol(s, &e, 10) != LONG_MAX || e != s + 19) + { + bad++; + say(2, "FAIL: strtol overflow clamp\n"); + } + s = "-9223372036854775809"; + if (strtol(s, &e, 10) != LONG_MIN || e != s + 20) + { + bad++; + say(2, "FAIL: strtol negative overflow clamp\n"); + } + s = "9223372036854775808"; + if (strtoll(s, &e, 10) != LLONG_MAX || e != s + 19) + { + bad++; + say(2, "FAIL: strtoll overflow clamp\n"); + } + s = "-9223372036854775809"; + if (strtoll(s, &e, 10) != LLONG_MIN || e != s + 20) + { + bad++; + say(2, "FAIL: strtoll negative overflow clamp\n"); + } + s = "18446744073709551616"; + if (strtoul(s, &e, 10) != ULONG_MAX || e != s + 20) + { + bad++; + say(2, "FAIL: strtoul overflow clamp\n"); + } + s = "18446744073709551617"; + if (strtoull(s, &e, 10) != ULLONG_MAX || e != s + 20) + { + bad++; + say(2, "FAIL: strtoull overflow clamp\n"); + } + s = "-18446744073709551616"; + if (strtoul(s, &e, 10) != ULONG_MAX || e != s + 21) + { + bad++; + say(2, "FAIL: strtoul magnitude overflow (2^64) clamp\n"); + } + s = "123"; + if (strtol(s, &e, 1) != 0 || e != s) + { + bad++; + say(2, "FAIL: strtol base 1 (EINVAL): value/endptr\n"); + } + s = "123"; + if (strtol(s, &e, 37) != 0 || e != s) + { + bad++; + say(2, "FAIL: strtol base 37 (EINVAL): value/endptr\n"); + } + s = "123"; + if (strtoul(s, &e, -1) != 0 || e != s) + { + bad++; + say(2, "FAIL: strtoul base -1 (EINVAL): value/endptr\n"); + } + s = "1e999"; + if (strtod(s, &e) != __builtin_inf() || e != s + 5) + { + bad++; + say(2, "FAIL: strtod(\"1e999\") != +inf (HUGE_VAL)\n"); + } + s = "-1e999"; + if (strtod(s, &e) != -__builtin_inf() || e != s + 6) + { + bad++; + say(2, "FAIL: strtod(\"-1e999\") != -inf\n"); + } + s = "1e-999"; + if (strtod(s, &e) != 0.0 || e != s + 6) + { + bad++; + say(2, "FAIL: strtod(\"1e-999\") != 0.0 (underflow)\n"); + } + s = "0x1.fffffffffffffp1024"; + if (strtod(s, &e) != __builtin_inf() || e != s + 22) + { + bad++; + say(2, "FAIL: strtod(hex overflow) != +inf\n"); + } + s = "0x0.0000000000001p-1022"; + if (strtod(s, &e) != DBL_TRUE_MIN || e != s + 23) + { + bad++; + say(2, "FAIL: strtod(hex subnormal) != DBL_TRUE_MIN\n"); + } + s = "1e999"; + if (strtof(s, &e) != __builtin_inff() || e != s + 5) + { + bad++; + say(2, "FAIL: strtof(\"1e999\") != +inf\n"); + } + s = "1e-999"; + if (strtof(s, &e) != 0.0f || e != s + 6) + { + bad++; + say(2, "FAIL: strtof(\"1e-999\") != 0.0f\n"); + } + s = "1e5000"; + if (strtold(s, &e) != __builtin_infl() || e != s + 6) + { + bad++; + say(2, "FAIL: strtold(\"1e5000\") != +inf\n"); + } + + if (bad == 0) + { + say(1, "PASS: all overflow/EINVAL/underflow failure scenarios\n"); + } + return bad > 0 ? 1 : 0; +} + +int +main(int argc, char **argv) +{ + int rc; + + if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f') + { + rc = failure_scenarios(); + __syscall1(SYS_exit_group, rc); + return rc; /* not reached */ + } + + base_resolution(); + whitespace_signs(); + endptr_positions(); + exact_boundaries(); + unsigned_negative_wrap(); + strtod_special(); + strtod_decimal(); + strtof_strtold(); + ato_wrappers(); + + if (failures > 0) + { + say(2, "FAILED ("); + say_dec(2, (unsigned long)failures); + say(2, " check(s))\n"); + return 1; + } + say(1, "all strtol/strtod tests passed\n"); + return 0; +}