| /**************************************************************************** |
| * libs/libc/stdlib/lib_strtold.c |
| * |
| * SPDX-License-Identifier: MIT |
| * SPDX-FileCopyrightText: 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 <nuttx/config.h> |
| #include <nuttx/compiler.h> |
| |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <ctype.h> |
| #include <stdlib.h> |
| #include <float.h> |
| #include <stdbool.h> |
| #include <errno.h> |
| #include <math.h> |
| |
| /**************************************************************************** |
| * 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 |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_HAVE_LONG_DOUBLE |
| |
| /**************************************************************************** |
| * 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 |
| * |
| ****************************************************************************/ |
| |
| double strtod(FAR const char *str, FAR char **endptr) |
| { |
| return strtox(str, endptr, 2); |
| } |
| |
| /**************************************************************************** |
| * 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 |
| * |
| ****************************************************************************/ |
| |
| long double strtold(FAR const char *str, FAR char **endptr) |
| { |
| return strtox(str, endptr, 3); |
| } |
| #endif /* CONFIG_HAVE_LONG_DOUBLE */ |