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