*** empty log message ***
[gnulib.git] / lib / strtod.c
1 /* Copyright (C) 1991, 1992, 1997, 1999, 2003 Free Software Foundation, Inc.
2
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; either version 2, or (at your option)
6    any later version.
7
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12
13    You should have received a copy of the GNU General Public License
14    along with this program; if not, write to the Free Software Foundation,
15    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
16
17 #if HAVE_CONFIG_H
18 # include <config.h>
19 #endif
20
21 #include <errno.h>
22 #ifndef errno
23 extern int errno;
24 #endif
25
26 #include <ctype.h>
27
28 #if defined (STDC_HEADERS) || (!defined (isascii) && !defined (HAVE_ISASCII))
29 # define IN_CTYPE_DOMAIN(c) 1
30 #else
31 # define IN_CTYPE_DOMAIN(c) isascii(c)
32 #endif
33
34 #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
35 #define ISDIGIT(c) (IN_CTYPE_DOMAIN (c) && isdigit (c))
36 #define TOLOWER(c) (IN_CTYPE_DOMAIN (c) ? tolower(c) : (c))
37
38 #include <math.h>
39
40 #include <float.h>
41 #include <stdlib.h>
42 #include <string.h>
43
44 /* Convert NPTR to a double.  If ENDPTR is not NULL, a pointer to the
45    character after the last one used in the number is put in *ENDPTR.  */
46 double
47 strtod (const char *nptr, char **endptr)
48 {
49   register const char *s;
50   short int sign;
51
52   /* The number so far.  */
53   double num;
54
55   int got_dot;                  /* Found a decimal point.  */
56   int got_digit;                /* Seen any digits.  */
57
58   /* The exponent of the number.  */
59   long int exponent;
60
61   if (nptr == NULL)
62     {
63       errno = EINVAL;
64       goto noconv;
65     }
66
67   s = nptr;
68
69   /* Eat whitespace.  */
70   while (ISSPACE (*s))
71     ++s;
72
73   /* Get the sign.  */
74   sign = *s == '-' ? -1 : 1;
75   if (*s == '-' || *s == '+')
76     ++s;
77
78   num = 0.0;
79   got_dot = 0;
80   got_digit = 0;
81   exponent = 0;
82   for (;; ++s)
83     {
84       if (ISDIGIT (*s))
85         {
86           got_digit = 1;
87
88           /* Make sure that multiplication by 10 will not overflow.  */
89           if (num > DBL_MAX * 0.1)
90             /* The value of the digit doesn't matter, since we have already
91                gotten as many digits as can be represented in a `double'.
92                This doesn't necessarily mean the result will overflow.
93                The exponent may reduce it to within range.
94
95                We just need to record that there was another
96                digit so that we can multiply by 10 later.  */
97             ++exponent;
98           else
99             num = (num * 10.0) + (*s - '0');
100
101           /* Keep track of the number of digits after the decimal point.
102              If we just divided by 10 here, we would lose precision.  */
103           if (got_dot)
104             --exponent;
105         }
106       else if (!got_dot && *s == '.')
107         /* Record that we have found the decimal point.  */
108         got_dot = 1;
109       else
110         /* Any other character terminates the number.  */
111         break;
112     }
113
114   if (!got_digit)
115     goto noconv;
116
117   if (TOLOWER (*s) == 'e')
118     {
119       /* Get the exponent specified after the `e' or `E'.  */
120       int save = errno;
121       char *end;
122       long int exp;
123
124       errno = 0;
125       ++s;
126       exp = strtol (s, &end, 10);
127       if (errno == ERANGE)
128         {
129           /* The exponent overflowed a `long int'.  It is probably a safe
130              assumption that an exponent that cannot be represented by
131              a `long int' exceeds the limits of a `double'.  */
132           if (endptr != NULL)
133             *endptr = end;
134           if (exp < 0)
135             goto underflow;
136           else
137             goto overflow;
138         }
139       else if (end == s)
140         /* There was no exponent.  Reset END to point to
141            the 'e' or 'E', so *ENDPTR will be set there.  */
142         end = (char *) s - 1;
143       errno = save;
144       s = end;
145       exponent += exp;
146     }
147
148   if (endptr != NULL)
149     *endptr = (char *) s;
150
151   if (num == 0.0)
152     return 0.0;
153
154   /* Multiply NUM by 10 to the EXPONENT power,
155      checking for overflow and underflow.  */
156
157   if (exponent < 0)
158     {
159       if (num < DBL_MIN * pow (10.0, (double) -exponent))
160         goto underflow;
161     }
162   else if (exponent > 0)
163     {
164       if (num > DBL_MAX * pow (10.0, (double) -exponent))
165         goto overflow;
166     }
167
168   num *= pow (10.0, (double) exponent);
169
170   return num * sign;
171
172 overflow:
173   /* Return an overflow error.  */
174   errno = ERANGE;
175   return HUGE_VAL * sign;
176
177 underflow:
178   /* Return an underflow error.  */
179   if (endptr != NULL)
180     *endptr = (char *) nptr;
181   errno = ERANGE;
182   return 0.0;
183
184 noconv:
185   /* There was no number.  */
186   if (endptr != NULL)
187     *endptr = (char *) nptr;
188   return 0.0;
189 }