/**************************************************************************** * libs/libc/stdlib/lib_strtold.c * Convert string to float and (long) double * * A pretty straight forward conversion of strtod(): * * Copyright © 2005-2020 Rich Felker, et al. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include /**************************************************************************** * Pre-processor definitions ****************************************************************************/ /* These are predefined with GCC, but could be issues for other compilers. If * not defined, an arbitrary big number is put in for now. These should be * added to nuttx/compiler for your compiler. */ #ifdef CONFIG_HAVE_LONG_DOUBLE # define long_double long double # define ldbl_max LDBL_MAX # define ldbl_min LDBL_MIN # define ldbl_min_exp LDBL_MIN_EXP # define ldbl_mant_dig LDBL_MANT_DIG # define ldbl_max_10_exp LDBL_MAX_10_EXP # define ldbl_min_10_exp LDBL_MIN_10_EXP #elif defined(CONFIG_HAVE_DOUBLE) # define long_double double # define ldbl_max DBL_MAX # define ldbl_min DBL_MIN # define ldbl_min_exp LDBL_MIN_EXP # define ldbl_mant_dig DBL_MANT_DIG # define ldbl_max_10_exp DBL_MAX_10_EXP # define ldbl_min_10_exp DBL_MIN_10_EXP #else # define long_double float # define ldbl_max FLT_MAX # define ldbl_min FLT_MIN # define ldbl_min_exp LDBL_MIN_EXP # define ldbl_mant_dig FLT_MANT_DIG # define ldbl_max_10_exp FLT_MAX_10_EXP # define ldbl_min_10_exp FLT_MIN_10_EXP #endif #ifdef CONFIG_HAVE_LONG_LONG # define long_long long long # define llong_min LLONG_MIN #else # define long_long long # define llong_min LONG_MIN #endif #define shgetc(f) (*(f)++) #define shunget(f) ((f)--) #define ifexist(a,b) do { if ((a) != NULL) {*(a) = (b);} } while (0) /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: scanexp * * Description: * Gets the sum number of a string which the value of * each character between 0 and 9 * * Input Parameters: * f - The string * flag - 1: The value of the f will be change, 0: not change * * Returned Value: * The sum number * ****************************************************************************/ static long_long scanexp(FAR char **f, bool flag) { FAR char *s = *f; int c; long_long y = 0; bool neg = 0; c = shgetc(s); if ((c == '+' || c == '-') && isdigit(*s)) { neg = (c == '-'); c = shgetc(s); } while (isdigit(c)) { y = 10 * y + c - '0'; c = shgetc(s); } shunget(s); if (flag) { ifexist(f, s); } return neg ? -y : y; } /**************************************************************************** * Name: ifallzero * * Description: * To find out if all the next number characters are all zero * * Input Parameters: * f - The string * flag - 1:The value of the f will be change , 0: not change * * Returned Value: * true - Yes (like: 0000st) * false - No (like: 0001st) * ****************************************************************************/ static bool ifallzero(FAR char **f, bool flag) { FAR char *s = *f; int c; c = shgetc(s); while (c == '0') { c = shgetc(s); } shunget(s); if (flag) { *f = s; } return !isdigit(c); } /**************************************************************************** * Name: chtou * * Description: * Determine whether c is a keyword then set the * value of the number add base * * Input Parameters: * c - Hexadecimal: 0 - f(F), decimal: 0 - 9 * base - Hexadecimal(16) decimal(10) * number - IF c is a keyword, number += (the value of c) * * Returned Value: * true - c is a keyword of hexadecimal or decimal * ****************************************************************************/ static bool chtou(char c, int base, FAR uint32_t *number) { int tmp = base; if (isdigit(c)) { tmp = c - '0'; } else if (c >= 'a' && c <= 'f') { tmp = c - 'a' + 10; } else if (c >= 'A' && c <= 'F') { tmp = c - 'A' + 10; } if (tmp >= base) { return false; } *number = *number * base + tmp; return true; } /**************************************************************************** * Name: scalbnx * * Description: * Implement functionality similar to scalbnl * * Returned Value: * Get a value of number * base ^ exp * ****************************************************************************/ static long_double scalbnx(long_double number, long_double base, long_long exp) { long_long e = exp; if (e < 0) { exp *= -1; } while (exp) { if (exp & 1) { if (e < 0) { number /= base; } else { number *= base; } } exp >>= 1; base *= base; } return number; } /**************************************************************************** * Name: decfloat * * Description: * Convert a decimal string to a long_double value * * Input Parameters: * ptr - A decimal string * endptr - If have ,the part that holds all but the numbers * * Returned Value: * A long_double number about ptr * ****************************************************************************/ static long_double decfloat(FAR char *ptr, FAR char **endptr) { FAR char *f; FAR char *s; int c; int k; int gotrad; uint32_t x; long_double y; long_long num_digit; long_long num_decimal; const long_double zero = 0.; const long p10s[] = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; f = ptr; num_digit = 0; num_decimal = 0; /* Don't let leading zeros consume buffer space */ ifallzero(&f, 1); c = shgetc(f); /* get the digit and decimal */ for (; isdigit(c); c = shgetc(f)) { num_digit++; } if (c == '.') { s = f; if (ifallzero(&f, 1)) { c = shgetc(f); num_digit++; } else { f = s; c = shgetc(f); for (; isdigit(c); c = shgetc(f)) { num_digit++; num_decimal--; } } } if ((c | 32) == 'e' && (isdigit(*f) || ((*f == '+' || *f == '-') && (isdigit(*(f + 1)))))) { num_decimal = scanexp(&f, 1) + num_decimal; if (num_decimal <= llong_min / 100) { ifexist(endptr, f); return zero; } } else { shunget(f); } ifexist(endptr, f); if (num_digit == 0) { return zero; } f = ptr; k = 0; x = 0; y = 0.; gotrad = 0; while (chtou(*f, 10, &x) || *f == '.') { if (*f == '.') { if (gotrad) { break; } c = shgetc(f); s = f; if (ifallzero(&s, 1)) { f = s; break; } gotrad = 1; continue; } f++; if (++k == 9) { k = 0; y = 1000000000 * y + x; x = 0; } } if (num_digit < 9 && num_decimal == 0) { return x; } else if (num_digit + num_decimal > ldbl_max_10_exp) { set_errno(ERANGE); } else if (num_digit + num_decimal < ldbl_min_10_exp) { set_errno(ERANGE); } if (k % 9) { y = y * p10s[k % 9 - 1] + x; } y *= 1.; y = scalbnx(y, 10., num_decimal); return y; } /**************************************************************************** * Name: hexfloat * * Description: * Convert a hexadecimal string to a long_double value * * Input Parameters: * ptr - The hexadecimal string * endptr - If have ,the part that holds all but the numbers * bits - LDBL_MANT_DIG * emin - LDBL_MIN_EXP - bits * * Returned Value: * A long_double number about ptr * ****************************************************************************/ static long_double hexfloat(FAR char *ptr, FAR char **endptr, int bits, int emin) { FAR char *f = ptr; int d; int c; int gottail = 0; int gotrad = 0; int gotdig = 0; uint32_t x = 0; long_double y = 0; long_double scale = 1; long_double bias = 0; long_long rp = 0; long_long dc = 0; long_long e2 = 0; const long_double zero = 0.; c = shgetc(f); /* Skip leading zeros */ for (; c == '0'; c = shgetc(f)) { gotdig = 1; } if (c == '.') { gotrad = 1; c = shgetc(f); /* Count zeros after the radix point before significand */ for (rp = 0; c == '0'; c = shgetc(f), rp--) { gotdig = 1; } } for (; isxdigit(c) || c == '.'; c = shgetc(f)) { if (c == '.') { if (gotrad) { break; } rp = dc; gotrad = 1; } else { gotdig = 1; if (c > '9') { d = (c | 32) + 10 - 'a'; } else { d = c - '0'; } if (dc < 8) { x = x * 16 + d; } else if (dc < ldbl_mant_dig / 4 + 1) { y += d * (scale /= 16); } else if (d && !gottail) { y += 0.5 * scale; gottail = 1; } dc++; } } if (!gotdig) { shunget(f); if (gotrad) { shunget(f); } ifexist(endptr, f); return zero; } if (!gotrad) { rp = dc; } while (dc < 8) { x *= 16, dc++; } if ((c | 32) == 'p') { e2 = scanexp(&f, 1); if (e2 == llong_min) { shunget(f); e2 = 0; } } else { shunget(f); } ifexist(endptr, f); e2 += 4 * rp - 32; if (!x) { return zero; } if (e2 > -emin) { set_errno(ERANGE); return ldbl_max * ldbl_max; } if (e2 < emin - 2 * ldbl_mant_dig) { set_errno(ERANGE); return ldbl_min * ldbl_min; } while (x < 0x80000000) { if (y >= 0.5) { x += x + 1; y += y - 1; } else { x += x; y += y; } e2--; } if (bits > 32 + e2 - emin) { bits = 32 + e2 - emin; if (bits < 0) { bits = 0; } } if (bits < ldbl_mant_dig) { bias = scalbnx(1, 2., 32 + ldbl_mant_dig - bits - 1); } if (bits < 32 && y && !(x & 1)) { x++, y = 0; } y = bias + x + y; y -= bias; /* If it's a wrong number we will set errno and return it */ if (!y) { set_errno(ERANGE); } return scalbnx(y, 2., e2); } /**************************************************************************** * Name: strtox * * Description: * Convert a string to a long_double value * * Input Parameters: * str - The string * endptr - If have ,the part that holds all but the numbers * flags - 1: string -> float * 2: string -> double * 3: string- > long double * * Returned Value: * A long_double number about str * ****************************************************************************/ static long_double strtox(FAR const char *str, FAR char **endptr, int flag) { FAR char *s = (FAR char *)str; bool negative = 0; long_double y = 0; int i = 0; int bits; int emin; switch (flag) { case 1: bits = FLT_MANT_DIG; emin = FLT_MIN_EXP - bits; break; case 2: bits = DBL_MANT_DIG, emin = DBL_MIN_EXP - bits; break; case 3: bits = LDBL_MANT_DIG, emin = LDBL_MIN_EXP - bits; break; default: return 0; } /* Skip leading whitespace */ while (isspace(*s)) { s++; } /* Handle optional sign */ switch (*s) { case '-': negative = 1; /* Fall through to increment position */ case '+': s++; default: break; } for (i = 0; i < 8 && (*s | 32) == "infinity"[i]; i++) { s++; } if (i == 3 || i == 8) { ifexist(endptr, s); return negative ? -INFINITY : INFINITY; } s -= i; for (i = 0; i < 3 && (*s | 32) == "nan"[i]; i++) { s++; } if (i == 3) { ifexist(endptr, s); return NAN; } /* Process optional 0x prefix */ s -= i; if (*s == '0' && (*(s + 1) | 32) == 'x') { s += 2; y = hexfloat(s, endptr, bits, emin); } else if (isdigit(*s) || (*s == '.' && isdigit(*(s + 1)))) { y = decfloat(s, endptr); } else { ifexist(endptr, (FAR char *)str); return 0; } return negative ? -y : y; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: strtof * * Description: * Convert a string to a float value * * Input Parameters: * str - The string * endptr - If have ,the part that holds all but the numbers * * Returned Value: * A float number about str * ****************************************************************************/ float strtof(FAR const char *str, FAR char **endptr) { return strtox(str, endptr, 1); } /**************************************************************************** * Name: strtod * * Description: * Convert a string to a double value * * Input Parameters: * str - The string * endptr - If have ,the part that holds all but the numbers * * Returned Value: * A double number about str * ****************************************************************************/ #ifdef CONFIG_HAVE_DOUBLE double strtod(FAR const char *str, FAR char **endptr) { return strtox(str, endptr, 2); } #endif /* CONFIG_HAVE_DOUBLE */ /**************************************************************************** * Name: strtold * * Description: * Convert a string to a long double value * * Input Parameters: * str - The string * endptr - If have ,the part that holds all but the numbers * * Returned Value: * A long double number about str * ****************************************************************************/ #ifdef CONFIG_HAVE_LONG_DOUBLE long double strtold(FAR const char *str, FAR char **endptr) { return strtox(str, endptr, 3); } #endif /* CONFIG_HAVE_LONG_DOUBLE */