exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / c-strtod.c
blob9551aebe37e22033af7908be78b0aa20622aeca7
1 /* Convert string to floating-point number, using the C locale.
3 Copyright (C) 2003-2004, 2006, 2009-2024 Free Software Foundation, Inc.
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18 /* Written by Paul Eggert and Bruno Haible. */
20 #include <config.h>
22 #include "c-strtod.h"
24 #include <errno.h>
25 #include <locale.h>
26 #include <stdlib.h>
27 #include <string.h>
29 #if FLOAT
30 # define C_STRTOD c_strtof
31 # define DOUBLE float
32 # define STRTOD_L strtof_l
33 # define HAVE_GOOD_STRTOD_L (HAVE_STRTOF_L && !GNULIB_defined_strtof_function)
34 # define STRTOD strtof
35 #elif LONG
36 # define C_STRTOD c_strtold
37 # define DOUBLE long double
38 # define STRTOD_L strtold_l
39 # define HAVE_GOOD_STRTOD_L (HAVE_STRTOLD_L && !GNULIB_defined_strtold_function)
40 # define STRTOD strtold
41 #else
42 # define C_STRTOD c_strtod
43 # define DOUBLE double
44 # define STRTOD_L strtod_l
45 # define HAVE_GOOD_STRTOD_L (HAVE_STRTOD_L && !GNULIB_defined_strtod_function)
46 # define STRTOD strtod
47 #endif
49 #if defined LC_ALL_MASK && (HAVE_GOOD_STRTOD_L || HAVE_WORKING_USELOCALE)
51 /* Cache for the C locale object.
52 Marked volatile so that different threads see the same value
53 (avoids locking). */
54 static volatile locale_t c_locale_cache;
56 /* Return the C locale object, or (locale_t) 0 with errno set
57 if it cannot be created. */
58 static locale_t
59 c_locale (void)
61 if (!c_locale_cache)
62 c_locale_cache = newlocale (LC_ALL_MASK, "C", (locale_t) 0);
63 return c_locale_cache;
66 #else
68 # if HAVE_NL_LANGINFO
69 # include <langinfo.h>
70 # endif
71 # include "c-ctype.h"
73 /* Determine the decimal-point character according to the current locale. */
74 static char
75 decimal_point_char (void)
77 const char *point;
78 /* Determine it in a multithread-safe way. We know nl_langinfo is
79 multithread-safe on glibc systems and Mac OS X systems, but is not required
80 to be multithread-safe by POSIX. sprintf(), however, is multithread-safe.
81 localeconv() is rarely multithread-safe. */
82 # if HAVE_NL_LANGINFO && (__GLIBC__ || defined __UCLIBC__ || (defined __APPLE__ && defined __MACH__))
83 point = nl_langinfo (RADIXCHAR);
84 # elif 1
85 char pointbuf[5];
86 sprintf (pointbuf, "%#.0f", 1.0);
87 point = &pointbuf[1];
88 # else
89 point = localeconv () -> decimal_point;
90 # endif
91 /* The decimal point is always a single byte: either '.' or ','. */
92 return (point[0] != '\0' ? point[0] : '.');
95 #endif
97 DOUBLE
98 C_STRTOD (char const *nptr, char **endptr)
100 DOUBLE r;
102 #if defined LC_ALL_MASK && (HAVE_GOOD_STRTOD_L || HAVE_WORKING_USELOCALE)
104 locale_t locale = c_locale ();
105 if (!locale)
107 if (endptr)
108 *endptr = (char *) nptr;
109 return 0; /* errno is set here */
112 # if HAVE_GOOD_STRTOD_L
114 r = STRTOD_L (nptr, endptr, locale);
116 # else /* HAVE_WORKING_USELOCALE */
118 locale_t old_locale = uselocale (locale);
119 if (old_locale == (locale_t)0)
121 if (endptr)
122 *endptr = (char *) nptr;
123 return 0; /* errno is set here */
126 r = STRTOD (nptr, endptr);
128 int saved_errno = errno;
129 if (uselocale (old_locale) == (locale_t)0)
130 /* We can't switch back to the old locale. The thread is hosed. */
131 abort ();
132 errno = saved_errno;
134 # endif
136 #else
138 char decimal_point = decimal_point_char ();
140 if (decimal_point == '.')
141 /* In this locale, C_STRTOD and STRTOD behave the same way. */
142 r = STRTOD (nptr, endptr);
143 else
145 /* Start and end of the floating-point number. */
146 char const *start;
147 char const *end;
148 /* Position of the decimal point '.' in the floating-point number.
149 Either decimal_point_p == NULL or start <= decimal_point_p < end. */
150 char const *decimal_point_p = NULL;
151 /* Set to true if we encountered decimal_point while parsing. */
152 int seen_decimal_point = 0;
154 /* Parse
155 1. a sequence of white-space characters,
156 2. a subject sequence possibly containing a floating-point number,
157 as described in POSIX
158 <https://pubs.opengroup.org/onlinepubs/9699919799/functions/strtod.html>.
161 char const *p;
163 p = nptr;
165 /* Parse a sequence of white-space characters. */
166 while (*p != '\0' && c_isspace ((unsigned char) *p))
167 p++;
168 start = p;
169 end = p;
171 /* Start to parse a subject sequence. */
172 if (*p == '+' || *p == '-')
173 p++;
174 if (*p == '0')
176 end = p + 1;
177 if (p[1] == 'x' || p[1] == 'X')
179 size_t num_hex_digits = 0;
180 p += 2;
181 /* Parse a non-empty sequence of hexadecimal digits optionally
182 containing the decimal point character '.'. */
183 while (*p != '\0')
185 if (c_isxdigit ((unsigned char) *p))
187 num_hex_digits++;
188 p++;
190 else if (*p == '.')
192 if (decimal_point_p == NULL)
194 decimal_point_p = p;
195 p++;
197 else
198 break;
200 else
201 break;
203 seen_decimal_point = (*p == decimal_point);
204 if (num_hex_digits > 0)
206 end = p;
207 /* Parse the character 'p' or the character 'P', optionally
208 followed by a '+' or '-' character, and then followed by
209 one or more decimal digits. */
210 if (*p == 'p' || *p == 'P')
212 p++;
213 if (*p == '+' || *p == '-')
214 p++;
215 if (*p != '\0' && c_isdigit ((unsigned char) *p))
217 p++;
218 while (*p != '\0' && c_isdigit ((unsigned char) *p))
219 p++;
220 end = p;
224 else
225 decimal_point_p = NULL;
226 goto done_parsing;
230 size_t num_digits = 0;
231 /* Parse a non-empty sequence of decimal digits optionally containing
232 the decimal point character '.'. */
233 while (*p != '\0')
235 if (c_isdigit ((unsigned char) *p))
237 num_digits++;
238 p++;
240 else if (*p == '.')
242 if (decimal_point_p == NULL)
244 decimal_point_p = p;
245 p++;
247 else
248 break;
250 else
251 break;
253 seen_decimal_point = (*p == decimal_point);
254 if (num_digits > 0)
256 end = p;
257 /* Parse the character 'e' or the character 'E', optionally
258 followed by a '+' or '-' character, and then followed by one or
259 more decimal digits. */
260 if (*p == 'e' || *p == 'E')
262 p++;
263 if (*p == '+' || *p == '-')
264 p++;
265 if (*p != '\0' && c_isdigit ((unsigned char) *p))
267 p++;
268 while (*p != '\0' && c_isdigit ((unsigned char) *p))
269 p++;
270 end = p;
274 else
275 decimal_point_p = NULL;
278 done_parsing:
279 if (end == start || (decimal_point_p == NULL && !seen_decimal_point))
280 /* If end == start, we have not parsed anything. The given string
281 might be "INF", "INFINITY", "NAN", "NAN(chars)", or invalid.
282 We can pass it to STRTOD.
283 If end > start and decimal_point_p == NULL, we have parsed a
284 floating-point number, and it does not have a '.' (and not a ','
285 either, of course). If !seen_decimal_point, we did not
286 encounter decimal_point while parsing. In this case, the
287 locale-dependent STRTOD function can handle it. */
288 r = STRTOD (nptr, endptr);
289 else
291 /* Create a modified floating-point number, in which the character '.'
292 is replaced with the locale-dependent decimal_point. */
293 size_t len = end - start;
294 char *buf;
295 char *buf_malloced = NULL;
296 char stackbuf[1000];
297 char *end_in_buf;
299 if (len < sizeof (stackbuf))
300 buf = stackbuf;
301 else
303 buf = malloc (len + 1);
304 if (buf == NULL)
306 /* Out of memory.
307 Callers may not be prepared to see errno == ENOMEM. But
308 they should be prepared to see errno == EINVAL. */
309 errno = EINVAL;
310 if (endptr != NULL)
311 *endptr = (char *) nptr;
312 return 0;
314 buf_malloced = buf;
317 memcpy (buf, start, len);
318 if (decimal_point_p != NULL)
319 buf[decimal_point_p - start] = decimal_point;
320 buf[len] = '\0';
322 r = STRTOD (buf, &end_in_buf);
323 if (endptr != NULL)
324 *endptr = (char *) (start + (end_in_buf - buf));
326 if (buf_malloced != NULL)
328 int saved_errno = errno;
329 free (buf_malloced);
330 errno = saved_errno;
335 #endif
337 return r;