801 lines
16 KiB
C
801 lines
16 KiB
C
/****************************************************************************
|
|
* 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 <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
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* 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 == '-')
|
|
{
|
|
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 (num_digit == 0)
|
|
{
|
|
shunget(f);
|
|
ifexist(endptr, f);
|
|
return zero;
|
|
}
|
|
|
|
if ((c | 32) == 'e')
|
|
{
|
|
num_decimal = scanexp(&f, 1) + num_decimal;
|
|
if (num_decimal <= llong_min / 100)
|
|
{
|
|
ifexist(endptr, f);
|
|
return zero;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
shunget(f);
|
|
}
|
|
|
|
ifexist(endptr, f);
|
|
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)
|
|
{
|
|
errno = ERANGE;
|
|
return ldbl_max * ldbl_max;
|
|
}
|
|
else if (num_digit + num_decimal < ldbl_min_10_exp)
|
|
{
|
|
errno = ERANGE;
|
|
return ldbl_min * ldbl_min;
|
|
}
|
|
|
|
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 (; c - '0' < 10 || (c | 32) - 'a' < 6 || 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);
|
|
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)
|
|
{
|
|
errno = ERANGE;
|
|
return ldbl_max * ldbl_max;
|
|
}
|
|
|
|
if (e2 < emin - 2 * ldbl_mant_dig)
|
|
{
|
|
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)
|
|
{
|
|
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;
|
|
int 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;
|
|
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
|
|
{
|
|
y = decfloat(s, endptr);
|
|
}
|
|
|
|
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 */ |