openat: don’t close (-1)
[gnulib.git] / lib / c-strtod.c
blobae74a5bd4d7dea00b2ec892d4e180c1d076b6ccc
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 <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
30 #if FLOAT
31 # define C_STRTOD c_strtof
32 # define DOUBLE float
33 # define STRTOD_L strtof_l
34 # define HAVE_GOOD_STRTOD_L (HAVE_STRTOF_L && !GNULIB_defined_strtof_function)
35 # define STRTOD strtof
36 #elif LONG
37 # define C_STRTOD c_strtold
38 # define DOUBLE long double
39 # define STRTOD_L strtold_l
40 # define HAVE_GOOD_STRTOD_L (HAVE_STRTOLD_L && !GNULIB_defined_strtold_function)
41 # define STRTOD strtold
42 #else
43 # define C_STRTOD c_strtod
44 # define DOUBLE double
45 # define STRTOD_L strtod_l
46 # define HAVE_GOOD_STRTOD_L (HAVE_STRTOD_L && !GNULIB_defined_strtod_function)
47 # define STRTOD strtod
48 #endif
50 /* Here we don't need the newlocale override that supports gl_locale_name(). */
51 #undef newlocale
53 #if defined LC_ALL_MASK && (HAVE_GOOD_STRTOD_L || HAVE_WORKING_USELOCALE)
55 /* Cache for the C locale object.
56 Marked volatile so that different threads see the same value
57 (avoids locking). */
58 static volatile locale_t c_locale_cache;
60 /* Return the C locale object, or (locale_t) 0 with errno set
61 if it cannot be created. */
62 static locale_t
63 c_locale (void)
65 if (!c_locale_cache)
66 c_locale_cache = newlocale (LC_ALL_MASK, "C", (locale_t) 0);
67 return c_locale_cache;
70 #else
72 # if HAVE_NL_LANGINFO
73 # include <langinfo.h>
74 # endif
75 # include "c-ctype.h"
77 /* Determine the decimal-point character according to the current locale. */
78 static char
79 decimal_point_char (void)
81 const char *point;
82 /* Determine it in a multithread-safe way. We know nl_langinfo is
83 multithread-safe on glibc systems and Mac OS X systems, but is not required
84 to be multithread-safe by POSIX. sprintf(), however, is multithread-safe.
85 localeconv() is rarely multithread-safe. */
86 # if HAVE_NL_LANGINFO && (__GLIBC__ || defined __UCLIBC__ || (defined __APPLE__ && defined __MACH__))
87 point = nl_langinfo (RADIXCHAR);
88 # elif 1
89 char pointbuf[5];
90 sprintf (pointbuf, "%#.0f", 1.0);
91 point = &pointbuf[1];
92 # else
93 point = localeconv () -> decimal_point;
94 # endif
95 /* The decimal point is always a single byte: either '.' or ','. */
96 return (point[0] != '\0' ? point[0] : '.');
99 #endif
101 DOUBLE
102 C_STRTOD (char const *nptr, char **endptr)
104 DOUBLE r;
106 #if defined LC_ALL_MASK && (HAVE_GOOD_STRTOD_L || HAVE_WORKING_USELOCALE)
108 locale_t locale = c_locale ();
109 if (!locale)
111 if (endptr)
112 *endptr = (char *) nptr;
113 return 0; /* errno is set here */
116 # if HAVE_GOOD_STRTOD_L
118 r = STRTOD_L (nptr, endptr, locale);
120 # else /* HAVE_WORKING_USELOCALE */
122 locale_t old_locale = uselocale (locale);
123 if (old_locale == (locale_t)0)
125 if (endptr)
126 *endptr = (char *) nptr;
127 return 0; /* errno is set here */
130 r = STRTOD (nptr, endptr);
132 int saved_errno = errno;
133 if (uselocale (old_locale) == (locale_t)0)
134 /* We can't switch back to the old locale. The thread is hosed. */
135 abort ();
136 errno = saved_errno;
138 # endif
140 #else
142 char decimal_point = decimal_point_char ();
144 if (decimal_point == '.')
145 /* In this locale, C_STRTOD and STRTOD behave the same way. */
146 r = STRTOD (nptr, endptr);
147 else
149 /* Start and end of the floating-point number. */
150 char const *start;
151 char const *end;
152 /* Position of the decimal point '.' in the floating-point number.
153 Either decimal_point_p == NULL or start <= decimal_point_p < end. */
154 char const *decimal_point_p = NULL;
155 /* Set to true if we encountered decimal_point while parsing. */
156 int seen_decimal_point = 0;
158 /* Parse
159 1. a sequence of white-space characters,
160 2. a subject sequence possibly containing a floating-point number,
161 as described in POSIX
162 <https://pubs.opengroup.org/onlinepubs/9699919799/functions/strtod.html>.
165 char const *p;
167 p = nptr;
169 /* Parse a sequence of white-space characters. */
170 while (*p != '\0' && c_isspace ((unsigned char) *p))
171 p++;
172 start = p;
173 end = p;
175 /* Start to parse a subject sequence. */
176 if (*p == '+' || *p == '-')
177 p++;
178 if (*p == '0')
180 end = p + 1;
181 if (p[1] == 'x' || p[1] == 'X')
183 size_t num_hex_digits = 0;
184 p += 2;
185 /* Parse a non-empty sequence of hexadecimal digits optionally
186 containing the decimal point character '.'. */
187 while (*p != '\0')
189 if (c_isxdigit ((unsigned char) *p))
191 num_hex_digits++;
192 p++;
194 else if (*p == '.')
196 if (decimal_point_p == NULL)
198 decimal_point_p = p;
199 p++;
201 else
202 break;
204 else
205 break;
207 seen_decimal_point = (*p == decimal_point);
208 if (num_hex_digits > 0)
210 end = p;
211 /* Parse the character 'p' or the character 'P', optionally
212 followed by a '+' or '-' character, and then followed by
213 one or more decimal digits. */
214 if (*p == 'p' || *p == 'P')
216 p++;
217 if (*p == '+' || *p == '-')
218 p++;
219 if (*p != '\0' && c_isdigit ((unsigned char) *p))
221 p++;
222 while (*p != '\0' && c_isdigit ((unsigned char) *p))
223 p++;
224 end = p;
228 else
229 decimal_point_p = NULL;
230 goto done_parsing;
234 size_t num_digits = 0;
235 /* Parse a non-empty sequence of decimal digits optionally containing
236 the decimal point character '.'. */
237 while (*p != '\0')
239 if (c_isdigit ((unsigned char) *p))
241 num_digits++;
242 p++;
244 else if (*p == '.')
246 if (decimal_point_p == NULL)
248 decimal_point_p = p;
249 p++;
251 else
252 break;
254 else
255 break;
257 seen_decimal_point = (*p == decimal_point);
258 if (num_digits > 0)
260 end = p;
261 /* Parse the character 'e' or the character 'E', optionally
262 followed by a '+' or '-' character, and then followed by one or
263 more decimal digits. */
264 if (*p == 'e' || *p == 'E')
266 p++;
267 if (*p == '+' || *p == '-')
268 p++;
269 if (*p != '\0' && c_isdigit ((unsigned char) *p))
271 p++;
272 while (*p != '\0' && c_isdigit ((unsigned char) *p))
273 p++;
274 end = p;
278 else
279 decimal_point_p = NULL;
282 done_parsing:
283 if (end == start || (decimal_point_p == NULL && !seen_decimal_point))
284 /* If end == start, we have not parsed anything. The given string
285 might be "INF", "INFINITY", "NAN", "NAN(chars)", or invalid.
286 We can pass it to STRTOD.
287 If end > start and decimal_point_p == NULL, we have parsed a
288 floating-point number, and it does not have a '.' (and not a ','
289 either, of course). If !seen_decimal_point, we did not
290 encounter decimal_point while parsing. In this case, the
291 locale-dependent STRTOD function can handle it. */
292 r = STRTOD (nptr, endptr);
293 else
295 /* Create a modified floating-point number, in which the character '.'
296 is replaced with the locale-dependent decimal_point. */
297 size_t len = end - start;
298 char *buf;
299 char *buf_malloced = NULL;
300 char stackbuf[1000];
301 char *end_in_buf;
303 if (len < sizeof (stackbuf))
304 buf = stackbuf;
305 else
307 buf = malloc (len + 1);
308 if (buf == NULL)
310 /* Out of memory.
311 Callers may not be prepared to see errno == ENOMEM. But
312 they should be prepared to see errno == EINVAL. */
313 errno = EINVAL;
314 if (endptr != NULL)
315 *endptr = (char *) nptr;
316 return 0;
318 buf_malloced = buf;
321 memcpy (buf, start, len);
322 if (decimal_point_p != NULL)
323 buf[decimal_point_p - start] = decimal_point;
324 buf[len] = '\0';
326 r = STRTOD (buf, &end_in_buf);
327 if (endptr != NULL)
328 *endptr = (char *) (start + (end_in_buf - buf));
330 if (buf_malloced != NULL)
332 int saved_errno = errno;
333 free (buf_malloced);
334 errno = saved_errno;
339 #endif
341 return r;