Branch data Line data Source code
1 : : #define _GNU_SOURCE
2 : : #include "libsupport.h"
3 : : #include <stdlib.h>
4 : : #include <locale.h>
5 : :
6 : : #if defined(__APPLE__) || defined(__FreeBSD__)
7 : : #include <xlocale.h>
8 : : #endif
9 : :
10 : : #ifdef __cplusplus
11 : : extern "C" {
12 : : #endif
13 : :
14 : : #if !defined(_OS_WINDOWS_)
15 : : // This code path should be used for systems that support the strtod_l function
16 : :
17 : : // Cache locale object
18 : : static int c_locale_initialized = 0;
19 : : static locale_t c_locale;
20 : :
21 : 381802 : locale_t get_c_locale(void)
22 : : {
23 [ + + ]: 381802 : if (!c_locale_initialized) {
24 : 30 : c_locale_initialized = 1;
25 : 30 : c_locale = newlocale(LC_ALL_MASK, "C", NULL);
26 : : }
27 : 381802 : return c_locale;
28 : : }
29 : :
30 : 381802 : JL_DLLEXPORT double jl_strtod_c(const char *nptr, char **endptr)
31 : : {
32 : 381802 : return strtod_l(nptr, endptr, get_c_locale());
33 : : }
34 : :
35 : 0 : JL_DLLEXPORT float jl_strtof_c(const char *nptr, char **endptr)
36 : : {
37 : 0 : return strtof_l(nptr, endptr, get_c_locale());
38 : : }
39 : :
40 : :
41 : : #else
42 : : // This code path should be used for systems that do not support the strtod_l function
43 : : // Currently this is MinGW/Windows
44 : :
45 : : // The following code is derived from the Python function _PyOS_ascii_strtod
46 : : // see https://github.com/python/cpython/blob/master/Python/pystrtod.c
47 : : //
48 : : // Copyright © 2001-2020 Python Software Foundation; All Rights Reserved
49 : : //
50 : : // The following modifications have been made:
51 : : // - Leading spaces are ignored
52 : : // - Parsing of hex floats is supported in the derived version
53 : : // - Python functions for tolower, isdigit and malloc have been replaced by the respective
54 : : // C stdlib functions
55 : :
56 : : #include <ctype.h>
57 : : #include <errno.h>
58 : :
59 : : int case_insensitive_match(const char *s, const char *t)
60 : : {
61 : : while (*t && tolower(*s) == *t) {
62 : : s++;
63 : : t++;
64 : : }
65 : : return *t ? 0 : 1;
66 : : }
67 : :
68 : : double parse_inf_or_nan(const char *p, char **endptr)
69 : : {
70 : : double retval;
71 : : const char *s;
72 : : int negate = 0;
73 : :
74 : : s = p;
75 : : if (*s == '-') {
76 : : negate = 1;
77 : : s++;
78 : : }
79 : : else if (*s == '+') {
80 : : s++;
81 : : }
82 : : if (case_insensitive_match(s, "inf")) {
83 : : s += 3;
84 : : if (case_insensitive_match(s, "inity"))
85 : : s += 5;
86 : : retval = negate ? -D_PINF : D_PINF;
87 : : }
88 : : else if (case_insensitive_match(s, "nan")) {
89 : : s += 3;
90 : : retval = negate ? -D_PNAN : D_PNAN;
91 : : }
92 : : else {
93 : : s = p;
94 : : retval = -1.0;
95 : : }
96 : : *endptr = (char *)s;
97 : : return retval;
98 : : }
99 : :
100 : :
101 : : JL_DLLEXPORT double jl_strtod_c(const char *nptr, char **endptr)
102 : : {
103 : : char *fail_pos;
104 : : double val;
105 : : struct lconv *locale_data;
106 : : const char *decimal_point;
107 : : size_t decimal_point_len;
108 : : const char *p, *decimal_point_pos;
109 : : const char *end = NULL; /* Silence gcc */
110 : : const char *digits_pos = NULL;
111 : : int negate = 0;
112 : :
113 : : fail_pos = NULL;
114 : :
115 : : locale_data = localeconv();
116 : : decimal_point = locale_data->decimal_point;
117 : : decimal_point_len = strlen(decimal_point);
118 : :
119 : : decimal_point_pos = NULL;
120 : :
121 : : p = nptr;
122 : :
123 : : /* parse leading spaces */
124 : : while (isspace((unsigned char)*p)) {
125 : : p++;
126 : : }
127 : :
128 : : /* Parse infinities and nans */
129 : : val = parse_inf_or_nan(p, endptr);
130 : : if (*endptr != p)
131 : : return val;
132 : :
133 : : /* Set errno to zero, so that we can distinguish zero results
134 : : and underflows */
135 : : errno = 0;
136 : :
137 : : /* We process the optional sign manually, then pass the remainder to
138 : : the system strtod. This ensures that the result of an underflow
139 : : has the correct sign. */
140 : :
141 : : /* Process leading sign, if present */
142 : : if (*p == '-') {
143 : : negate = 1;
144 : : p++;
145 : : }
146 : : else if (*p == '+') {
147 : : p++;
148 : : }
149 : :
150 : : /* This code path is used for hex floats */
151 : : if (*p == '0' && (*(p+1) == 'x' || *(p+1) == 'X')) {
152 : : digits_pos = p;
153 : : p += 2;
154 : : /* Check that what's left begins with a digit or decimal point */
155 : : if (!isxdigit(*p) && *p != '.')
156 : : goto invalid_string;
157 : :
158 : :
159 : : if (decimal_point[0] != '.' || decimal_point[1] != 0) {
160 : : /* Look for a '.' in the input; if present, it'll need to be
161 : : swapped for the current locale's decimal point before we
162 : : call strtod. On the other hand, if we find the current
163 : : locale's decimal point then the input is invalid. */
164 : : while (isxdigit(*p))
165 : : p++;
166 : :
167 : : if (*p == '.') {
168 : : decimal_point_pos = p++;
169 : :
170 : : /* locate end of number */
171 : : while (isxdigit(*p))
172 : : p++;
173 : :
174 : : if (*p == 'p' || *p == 'P')
175 : : p++;
176 : : if (*p == '+' || *p == '-')
177 : : p++;
178 : : while (isdigit(*p))
179 : : p++;
180 : : end = p;
181 : : }
182 : : else if (strncmp(p, decimal_point, decimal_point_len) == 0)
183 : : goto invalid_string;
184 : : /* For the other cases, we need not convert the decimal point */
185 : : }
186 : : }
187 : : else {
188 : : /* Check that what's left begins with a digit or decimal point */
189 : : if (!isdigit(*p) && *p != '.')
190 : : goto invalid_string;
191 : :
192 : : digits_pos = p;
193 : : if (decimal_point[0] != '.' || decimal_point[1] != 0) {
194 : : /* Look for a '.' in the input; if present, it'll need to be
195 : : swapped for the current locale's decimal point before we
196 : : call strtod. On the other hand, if we find the current
197 : : locale's decimal point then the input is invalid. */
198 : : while (isdigit(*p))
199 : : p++;
200 : :
201 : : if (*p == '.') {
202 : : decimal_point_pos = p++;
203 : :
204 : : /* locate end of number */
205 : : while (isdigit(*p))
206 : : p++;
207 : :
208 : : if (*p == 'e' || *p == 'E')
209 : : p++;
210 : : if (*p == '+' || *p == '-')
211 : : p++;
212 : : while (isdigit(*p))
213 : : p++;
214 : : end = p;
215 : : }
216 : : else if (strncmp(p, decimal_point, decimal_point_len) == 0)
217 : : goto invalid_string;
218 : : /* For the other cases, we need not convert the decimal point */
219 : : }
220 : : }
221 : :
222 : : if (decimal_point_pos) {
223 : : char *copy, *c;
224 : : /* Create a copy of the input, with the '.' converted to the
225 : : locale-specific decimal point */
226 : : copy = (char *)malloc(end - digits_pos + 1 + decimal_point_len);
227 : : if (copy == NULL) {
228 : : *endptr = (char *)nptr;
229 : : errno = ENOMEM;
230 : : return -1.0;
231 : : }
232 : :
233 : : c = copy;
234 : : memcpy(c, digits_pos, decimal_point_pos - digits_pos);
235 : : c += decimal_point_pos - digits_pos;
236 : : memcpy(c, decimal_point, decimal_point_len);
237 : : c += decimal_point_len;
238 : : memcpy(c, decimal_point_pos + 1,
239 : : end - (decimal_point_pos + 1));
240 : : c += end - (decimal_point_pos + 1);
241 : : *c = 0;
242 : :
243 : : val = strtod(copy, &fail_pos);
244 : :
245 : : if (fail_pos)
246 : : {
247 : : if (fail_pos > decimal_point_pos)
248 : : fail_pos = (char *)digits_pos +
249 : : (fail_pos - copy) -
250 : : (decimal_point_len - 1);
251 : : else
252 : : fail_pos = (char *)digits_pos +
253 : : (fail_pos - copy);
254 : : }
255 : :
256 : : free(copy);
257 : : }
258 : : else {
259 : : val = strtod(digits_pos, &fail_pos);
260 : : }
261 : :
262 : : if (fail_pos == digits_pos)
263 : : goto invalid_string;
264 : :
265 : : if (negate && fail_pos != nptr)
266 : : val = -val;
267 : : *endptr = fail_pos;
268 : :
269 : : return val;
270 : :
271 : : invalid_string:
272 : : *endptr = (char*)nptr;
273 : : errno = EINVAL;
274 : : return -1.0;
275 : : }
276 : :
277 : :
278 : : JL_DLLEXPORT float jl_strtof_c(const char *nptr, char **endptr)
279 : : {
280 : : return (float) jl_strtod_c(nptr, endptr);
281 : : }
282 : :
283 : : #endif
284 : :
285 : : #ifdef __cplusplus
286 : : }
287 : : #endif
|