diff options
Diffstat (limited to 'lib/strtod.c')
-rw-r--r-- | lib/strtod.c | 486 |
1 files changed, 486 insertions, 0 deletions
diff --git a/lib/strtod.c b/lib/strtod.c new file mode 100644 index 0000000..9b3a142 --- /dev/null +++ b/lib/strtod.c @@ -0,0 +1,486 @@ +/* Copyright (C) 1991-1992, 1997, 1999, 2003, 2006, 2008-2022 Free Software + Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +#if ! defined USE_LONG_DOUBLE +# include <config.h> +#endif + +/* Specification. */ +#include <stdlib.h> + +#include <ctype.h> /* isspace() */ +#include <errno.h> +#include <float.h> /* {DBL,LDBL}_{MIN,MAX} */ +#include <limits.h> /* LONG_{MIN,MAX} */ +#include <locale.h> /* localeconv() */ +#include <math.h> /* NAN */ +#include <stdbool.h> +#include <stdio.h> /* sprintf() */ +#include <string.h> /* strdup() */ +#if HAVE_NL_LANGINFO +# include <langinfo.h> +#endif + +#include "c-ctype.h" + +#undef MIN +#undef MAX +#ifdef USE_LONG_DOUBLE +# define STRTOD strtold +# define LDEXP ldexpl +# if defined __hpux && defined __hppa + /* We cannot call strtold on HP-UX/hppa, because its return type is a struct, + not a 'long double'. */ +# define HAVE_UNDERLYING_STRTOD 0 +# elif STRTOLD_HAS_UNDERFLOW_BUG + /* strtold would not set errno=ERANGE upon underflow. */ +# define HAVE_UNDERLYING_STRTOD 0 +# else +# define HAVE_UNDERLYING_STRTOD HAVE_STRTOLD +# endif +# define DOUBLE long double +# define MIN LDBL_MIN +# define MAX LDBL_MAX +# define L_(literal) literal##L +#else +# define STRTOD strtod +# define LDEXP ldexp +# define HAVE_UNDERLYING_STRTOD 1 +# define DOUBLE double +# define MIN DBL_MIN +# define MAX DBL_MAX +# define L_(literal) literal +#endif + +#if (defined USE_LONG_DOUBLE ? HAVE_LDEXPM_IN_LIBC : HAVE_LDEXP_IN_LIBC) +# define USE_LDEXP 1 +#else +# define USE_LDEXP 0 +#endif + +/* Return true if C is a space in the current locale, avoiding + problems with signed char and isspace. */ +static bool +locale_isspace (char c) +{ + unsigned char uc = c; + return isspace (uc) != 0; +} + +/* Determine the decimal-point character according to the current locale. */ +static char +decimal_point_char (void) +{ + const char *point; + /* Determine it in a multithread-safe way. We know nl_langinfo is + multithread-safe on glibc systems and Mac OS X systems, but is not required + to be multithread-safe by POSIX. sprintf(), however, is multithread-safe. + localeconv() is rarely multithread-safe. */ +#if HAVE_NL_LANGINFO && (__GLIBC__ || defined __UCLIBC__ || (defined __APPLE__ && defined __MACH__)) + point = nl_langinfo (RADIXCHAR); +#elif 1 + char pointbuf[5]; + sprintf (pointbuf, "%#.0f", 1.0); + point = &pointbuf[1]; +#else + point = localeconv () -> decimal_point; +#endif + /* The decimal point is always a single byte: either '.' or ','. */ + return (point[0] != '\0' ? point[0] : '.'); +} + +#if !USE_LDEXP + #undef LDEXP + #define LDEXP dummy_ldexp + /* A dummy definition that will never be invoked. */ + static DOUBLE LDEXP (_GL_UNUSED DOUBLE x, _GL_UNUSED int exponent) + { + abort (); + return L_(0.0); + } +#endif + +/* Return X * BASE**EXPONENT. Return an extreme value and set errno + to ERANGE if underflow or overflow occurs. */ +static DOUBLE +scale_radix_exp (DOUBLE x, int radix, long int exponent) +{ + /* If RADIX == 10, this code is neither precise nor fast; it is + merely a straightforward and relatively portable approximation. + If N == 2, this code is precise on a radix-2 implementation, + albeit perhaps not fast if ldexp is not in libc. */ + + long int e = exponent; + + if (USE_LDEXP && radix == 2) + return LDEXP (x, e < INT_MIN ? INT_MIN : INT_MAX < e ? INT_MAX : e); + else + { + DOUBLE r = x; + + if (r != 0) + { + if (e < 0) + { + while (e++ != 0) + { + r /= radix; + if (r == 0 && x != 0) + { + errno = ERANGE; + break; + } + } + } + else + { + while (e-- != 0) + { + if (r < -MAX / radix) + { + errno = ERANGE; + return -HUGE_VAL; + } + else if (MAX / radix < r) + { + errno = ERANGE; + return HUGE_VAL; + } + else + r *= radix; + } + } + } + + return r; + } +} + +/* Parse a number at NPTR; this is a bit like strtol (NPTR, ENDPTR) + except there are no leading spaces or signs or "0x", and ENDPTR is + nonnull. The number uses a base BASE (either 10 or 16) fraction, a + radix RADIX (either 10 or 2) exponent, and exponent character + EXPCHAR. BASE is RADIX**RADIX_MULTIPLIER. */ +static DOUBLE +parse_number (const char *nptr, + int base, int radix, int radix_multiplier, char radixchar, + char expchar, + char **endptr) +{ + const char *s = nptr; + const char *digits_start; + const char *digits_end; + const char *radixchar_ptr; + long int exponent; + DOUBLE num; + + /* First, determine the start and end of the digit sequence. */ + digits_start = s; + radixchar_ptr = NULL; + for (;; ++s) + { + if (base == 16 ? c_isxdigit (*s) : c_isdigit (*s)) + ; + else if (radixchar_ptr == NULL && *s == radixchar) + { + /* Record that we have found the decimal point. */ + radixchar_ptr = s; + } + else + /* Any other character terminates the digit sequence. */ + break; + } + digits_end = s; + /* Now radixchar_ptr == NULL or + digits_start <= radixchar_ptr < digits_end. */ + + if (false) + { /* Unoptimized. */ + exponent = + (radixchar_ptr != NULL + ? - (long int) (digits_end - radixchar_ptr - 1) + : 0); + } + else + { /* Remove trailing zero digits. This reduces rounding errors for + inputs such as 1.0000000000 or 10000000000e-10. */ + while (digits_end > digits_start) + { + if (digits_end - 1 == radixchar_ptr || *(digits_end - 1) == '0') + digits_end--; + else + break; + } + exponent = + (radixchar_ptr != NULL + ? (digits_end > radixchar_ptr + ? - (long int) (digits_end - radixchar_ptr - 1) + : (long int) (radixchar_ptr - digits_end)) + : (long int) (s - digits_end)); + } + + /* Then, convert the digit sequence to a number. */ + { + const char *dp; + num = 0; + for (dp = digits_start; dp < digits_end; dp++) + if (dp != radixchar_ptr) + { + int digit; + + /* Make sure that multiplication by BASE will not overflow. */ + if (!(num <= MAX / base)) + { + /* The value of the digit and all subsequent digits don't matter, + since we have already gotten as many digits as can be + represented in a 'DOUBLE'. This doesn't necessarily mean that + the result will overflow: The exponent may reduce it to within + range. */ + exponent += + (digits_end - dp) + - (radixchar_ptr >= dp && radixchar_ptr < digits_end ? 1 : 0); + break; + } + + /* Eat the next digit. */ + if (c_isdigit (*dp)) + digit = *dp - '0'; + else if (base == 16 && c_isxdigit (*dp)) + digit = c_tolower (*dp) - ('a' - 10); + else + abort (); + num = num * base + digit; + } + } + + exponent = exponent * radix_multiplier; + + /* Finally, parse the exponent. */ + if (c_tolower (*s) == expchar && ! locale_isspace (s[1])) + { + /* Add any given exponent to the implicit one. */ + int saved_errno = errno; + char *end; + long int value = strtol (s + 1, &end, 10); + errno = saved_errno; + + if (s + 1 != end) + { + /* Skip past the exponent, and add in the implicit exponent, + resulting in an extreme value on overflow. */ + s = end; + exponent = + (exponent < 0 + ? (value < LONG_MIN - exponent ? LONG_MIN : exponent + value) + : (LONG_MAX - exponent < value ? LONG_MAX : exponent + value)); + } + } + + *endptr = (char *) s; + return scale_radix_exp (num, radix, exponent); +} + +/* HP cc on HP-UX 10.20 has a bug with the constant expression -0.0. + ICC 10.0 has a bug when optimizing the expression -zero. + The expression -MIN * MIN does not work when cross-compiling + to PowerPC on Mac OS X 10.5. */ +static DOUBLE +minus_zero (void) +{ +#if defined __hpux || defined __sgi || defined __ICC + return -MIN * MIN; +#else + return -0.0; +#endif +} + +/* Convert NPTR to a DOUBLE. If ENDPTR is not NULL, a pointer to the + character after the last one used in the number is put in *ENDPTR. */ +DOUBLE +STRTOD (const char *nptr, char **endptr) +#if HAVE_UNDERLYING_STRTOD +# ifdef USE_LONG_DOUBLE +# undef strtold +# else +# undef strtod +# endif +#else +# undef STRTOD +# define STRTOD(NPTR,ENDPTR) \ + parse_number (NPTR, 10, 10, 1, radixchar, 'e', ENDPTR) +#endif +/* From here on, STRTOD refers to the underlying implementation. It needs + to handle only finite unsigned decimal numbers with non-null ENDPTR. */ +{ + char radixchar; + bool negative = false; + + /* The number so far. */ + DOUBLE num; + + const char *s = nptr; + const char *end; + char *endbuf; + int saved_errno = errno; + + radixchar = decimal_point_char (); + + /* Eat whitespace. */ + while (locale_isspace (*s)) + ++s; + + /* Get the sign. */ + negative = *s == '-'; + if (*s == '-' || *s == '+') + ++s; + + num = STRTOD (s, &endbuf); + end = endbuf; + + if (c_isdigit (s[*s == radixchar])) + { + /* If a hex float was converted incorrectly, do it ourselves. + If the string starts with "0x" but does not contain digits, + consume the "0" ourselves. If a hex float is followed by a + 'p' but no exponent, then adjust the end pointer. */ + if (*s == '0' && c_tolower (s[1]) == 'x') + { + if (! c_isxdigit (s[2 + (s[2] == radixchar)])) + { + end = s + 1; + + /* strtod() on z/OS returns ERANGE for "0x". */ + errno = saved_errno; + } + else if (end <= s + 2) + { + num = parse_number (s + 2, 16, 2, 4, radixchar, 'p', &endbuf); + end = endbuf; + } + else + { + const char *p = s + 2; + while (p < end && c_tolower (*p) != 'p') + p++; + if (p < end && ! c_isdigit (p[1 + (p[1] == '-' || p[1] == '+')])) + { + char *dup = strdup (s); + errno = saved_errno; + if (!dup) + { + /* Not really our day, is it. Rounding errors are + better than outright failure. */ + num = + parse_number (s + 2, 16, 2, 4, radixchar, 'p', &endbuf); + } + else + { + dup[p - s] = '\0'; + num = STRTOD (dup, &endbuf); + saved_errno = errno; + free (dup); + errno = saved_errno; + } + end = p; + } + } + } + else + { + /* If "1e 1" was misparsed as 10.0 instead of 1.0, re-do the + underlying STRTOD on a copy of the original string + truncated to avoid the bug. */ + const char *e = s + 1; + while (e < end && c_tolower (*e) != 'e') + e++; + if (e < end && ! c_isdigit (e[1 + (e[1] == '-' || e[1] == '+')])) + { + char *dup = strdup (s); + errno = saved_errno; + if (!dup) + { + /* Not really our day, is it. Rounding errors are + better than outright failure. */ + num = parse_number (s, 10, 10, 1, radixchar, 'e', &endbuf); + } + else + { + dup[e - s] = '\0'; + num = STRTOD (dup, &endbuf); + saved_errno = errno; + free (dup); + errno = saved_errno; + } + end = e; + } + } + + s = end; + } + + /* Check for infinities and NaNs. */ + else if (c_tolower (*s) == 'i' + && c_tolower (s[1]) == 'n' + && c_tolower (s[2]) == 'f') + { + s += 3; + if (c_tolower (*s) == 'i' + && c_tolower (s[1]) == 'n' + && c_tolower (s[2]) == 'i' + && c_tolower (s[3]) == 't' + && c_tolower (s[4]) == 'y') + s += 5; + num = HUGE_VAL; + errno = saved_errno; + } + else if (c_tolower (*s) == 'n' + && c_tolower (s[1]) == 'a' + && c_tolower (s[2]) == 'n') + { + s += 3; + if (*s == '(') + { + const char *p = s + 1; + while (c_isalnum (*p)) + p++; + if (*p == ')') + s = p + 1; + } + + /* If the underlying implementation misparsed the NaN, assume + its result is incorrect, and return a NaN. Normally it's + better to use the underlying implementation's result, since a + nice implementation populates the bits of the NaN according + to interpreting n-char-sequence as a hexadecimal number. */ + if (s != end || num == num) + num = NAN; + errno = saved_errno; + } + else + { + /* No conversion could be performed. */ + errno = EINVAL; + s = nptr; + } + + if (endptr != NULL) + *endptr = (char *) s; + /* Special case -0.0, since at least ICC miscompiles negation. We + can't use copysign(), as that drags in -lm on some platforms. */ + if (!num && negative) + return minus_zero (); + return negative ? -num : num; +} |