Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: CPL - Common Portability Library
4 : * Purpose: Functions to convert ASCII string to floating point number.
5 : * Author: Andrey Kiselev, dron@ak4719.spb.edu.
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2006, Andrey Kiselev
9 : * Copyright (c) 2008-2012, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "cpl_port.h"
15 : #include "cpl_conv.h"
16 : #include "cpl_string.h"
17 :
18 : #include <cerrno>
19 : #include <clocale>
20 : #include <cstring>
21 : #include <cstdlib>
22 : #include <limits>
23 : #include <optional>
24 :
25 : // Coverity complains about CPLAtof(CPLGetConfigOption(...)) causing
26 : // a "untrusted loop bound" in the loop "Find a reasonable position for the end
27 : // of the string to provide to fast_float"
28 : #ifndef __COVERITY__
29 : #define USE_FAST_FLOAT
30 : #endif
31 :
32 : #ifdef USE_FAST_FLOAT
33 : #include "include_fast_float.h"
34 : #endif
35 :
36 : #include "cpl_config.h"
37 :
38 : /************************************************************************/
39 : /* CPLAtofDelim() */
40 : /************************************************************************/
41 :
42 : /**
43 : * Converts ASCII string to floating point number.
44 : *
45 : * This function converts the initial portion of the string pointed to
46 : * by nptr to double floating point representation. The behavior is the
47 : * same as
48 : *
49 : * CPLStrtodDelim(nptr, (char **)NULL, point);
50 : *
51 : * This function does the same as standard atof(3), but does not take locale
52 : * in account. Instead of locale defined decimal delimiter you can specify
53 : * your own one. Also see notes for CPLAtof() function.
54 : *
55 : * @param nptr Pointer to string to convert.
56 : * @param point Decimal delimiter.
57 : *
58 : * @return Converted value, if any.
59 : */
60 169899 : double CPLAtofDelim(const char *nptr, char point)
61 : {
62 169899 : return CPLStrtodDelim(nptr, nullptr, point);
63 : }
64 :
65 : /************************************************************************/
66 : /* CPLAtof() */
67 : /************************************************************************/
68 :
69 : /**
70 : * Converts ASCII string to floating point number.
71 : *
72 : * This function converts the initial portion of the string pointed to
73 : * by nptr to double floating point representation. The behavior is the
74 : * same as
75 : *
76 : * CPLStrtod(nptr, (char **)NULL);
77 : *
78 : * This function does the same as standard atof(3), but does not take
79 : * locale in account. That means, the decimal delimiter is always '.'
80 : * (decimal point). Use CPLAtofDelim() function if you want to specify
81 : * custom delimiter.
82 : *
83 : * IMPORTANT NOTE:
84 : *
85 : * Existence of this function does not mean you should always use it. Sometimes
86 : * you should use standard locale aware atof(3) and its family. When you need to
87 : * process the user's input (for example, command line parameters) use atof(3),
88 : * because the user works in a localized environment and the user's input will
89 : * be done according to the locale set. In particular that means we should not
90 : * make assumptions about character used as decimal delimiter, it can be either
91 : * "." or ",".
92 : *
93 : * But when you are parsing some ASCII file in predefined format, you most
94 : * likely need CPLAtof(), because such files distributed across the systems
95 : * with different locales and floating point representation should be
96 : * considered as a part of file format. If the format uses "." as a delimiter
97 : * the same character must be used when parsing number regardless of actual
98 : * locale setting.
99 : *
100 : * @param nptr Pointer to string to convert.
101 : *
102 : * @return Converted value, if any.
103 : */
104 1034800 : double CPLAtof(const char *nptr)
105 : {
106 1034800 : return CPLStrtod(nptr, nullptr);
107 : }
108 :
109 : /************************************************************************/
110 : /* CPLAtofM() */
111 : /************************************************************************/
112 :
113 : /**
114 : * Converts ASCII string to floating point number using any numeric locale.
115 : *
116 : * This function converts the initial portion of the string pointed to
117 : * by nptr to double floating point representation. This function does the
118 : * same as standard atof(), but it allows a variety of locale representations.
119 : * That is it supports numeric values with either a comma or a period for
120 : * the decimal delimiter.
121 : *
122 : * PS. The M stands for Multi-lingual.
123 : *
124 : * @param nptr The string to convert.
125 : *
126 : * @return Converted value, if any. Zero on failure.
127 : */
128 :
129 26634 : double CPLAtofM(const char *nptr)
130 :
131 : {
132 26634 : const int nMaxSearch = 50;
133 :
134 87357 : for (int i = 0; i < nMaxSearch; i++)
135 : {
136 87357 : if (nptr[i] == ',')
137 412 : return CPLStrtodDelim(nptr, nullptr, ',');
138 86945 : if (nptr[i] == '.' || nptr[i] == '\0')
139 26222 : return CPLStrtodDelim(nptr, nullptr, '.');
140 : }
141 :
142 0 : return CPLStrtodDelim(nptr, nullptr, '.');
143 : }
144 :
145 : /************************************************************************/
146 : /* CPLReplacePointByLocalePoint() */
147 : /************************************************************************/
148 :
149 : /* Return a newly allocated variable if substitution was done, or NULL
150 : * otherwise.
151 : */
152 10 : static char *CPLReplacePointByLocalePoint(const char *pszNumber, char point)
153 : {
154 : #if defined(__ANDROID__) && __ANDROID_API__ < 20
155 : // localeconv() only available since API 20
156 : static char byPoint = 0;
157 : if (byPoint == 0)
158 : {
159 : char szBuf[16] = {};
160 : snprintf(szBuf, sizeof(szBuf), "%.1f", 1.0);
161 : byPoint = szBuf[1];
162 : }
163 : if (point != byPoint)
164 : {
165 : const char *pszPoint = strchr(pszNumber, point);
166 : if (pszPoint)
167 : {
168 : char *pszNew = CPLStrdup(pszNumber);
169 : pszNew[pszPoint - pszNumber] = byPoint;
170 : return pszNew;
171 : }
172 : }
173 : #else // ndef __ANDROID__
174 10 : struct lconv *poLconv = localeconv();
175 10 : if (poLconv && poLconv->decimal_point && poLconv->decimal_point[0] != '\0')
176 : {
177 10 : char byPoint = poLconv->decimal_point[0];
178 :
179 10 : if (point != byPoint)
180 : {
181 0 : const char *pszLocalePoint = strchr(pszNumber, byPoint);
182 0 : const char *pszPoint = strchr(pszNumber, point);
183 0 : if (pszPoint || pszLocalePoint)
184 : {
185 0 : char *pszNew = CPLStrdup(pszNumber);
186 0 : if (pszLocalePoint)
187 0 : pszNew[pszLocalePoint - pszNumber] = ' ';
188 0 : if (pszPoint)
189 0 : pszNew[pszPoint - pszNumber] = byPoint;
190 0 : return pszNew;
191 : }
192 : }
193 : }
194 : #endif // __ANDROID__
195 :
196 10 : return nullptr;
197 : }
198 :
199 : /************************************************************************/
200 : /* CPLStrtodDelim() */
201 : /************************************************************************/
202 :
203 : /**
204 : * Converts ASCII string to floating point number using specified delimiter.
205 : *
206 : * This function converts the initial portion of the string pointed to
207 : * by nptr to double floating point representation. This function does the
208 : * same as standard strtod(3), but does not take locale into account. Instead of
209 : * the locale-defined decimal delimiter you can specify your own one. Also see
210 : * notes for CPLAtof() function.
211 : *
212 : * @param nptr Pointer to string to convert.
213 : * @param endptr If is not NULL, a pointer to the character after the last
214 : * character used in the conversion is stored in the location referenced
215 : * by endptr.
216 : * @param point Decimal delimiter.
217 : *
218 : * @return Converted value, if any.
219 : */
220 1732130 : double CPLStrtodDelim(const char *nptr, char **endptr, char point)
221 : {
222 1732130 : return cpl::strtod_delim(nptr, endptr, point);
223 : }
224 :
225 : namespace cpl
226 : {
227 : /**
228 : * Converts ASCII string to floating point number using specified delimiter.
229 : *
230 : * This function converts the initial portion of the string pointed to
231 : * by nptr to double floating point representation. This function does the
232 : * same as standard strtod(3), but does not take locale into account. Instead of
233 : * the locale-defined decimal delimiter you can specify your own one. Also see
234 : * notes for CPLAtof() function.
235 : *
236 : * @param str String view to convert.
237 : * @param endptr If is not NULL, a pointer to the character after the last
238 : * character used in the conversion is stored in the location referenced
239 : * by endptr.
240 : * @param point Decimal delimiter.
241 : *
242 : * @return Converted value, if any.
243 : */
244 1732130 : double strtod_delim(std::string_view str, char **endptr, char point)
245 : {
246 1732130 : str = trim(str);
247 :
248 1732130 : if (str.empty())
249 : {
250 14410 : if (endptr)
251 61 : *endptr = const_cast<char *>(str.data());
252 14410 : return 0;
253 : }
254 :
255 1717720 : if (str[0] == '-')
256 : {
257 174003 : if (starts_with(str, "-1.#QNAN") || starts_with(str, "-1.#IND"))
258 : {
259 2 : if (endptr)
260 2 : *endptr = const_cast<char *>(str.data()) + str.size();
261 : // While it is possible on some platforms to flip the sign
262 : // of NAN to negative, this function will always return a positive
263 : // quiet (non-signalling) NaN.
264 2 : return std::numeric_limits<double>::quiet_NaN();
265 : }
266 174001 : if (starts_with_ci(str, "-1.#INF"))
267 : {
268 3 : if (endptr)
269 1 : *endptr = const_cast<char *>(str.data()) + str.size();
270 3 : return -std::numeric_limits<double>::infinity();
271 : }
272 : }
273 1543710 : else if (str[0] == '1')
274 : {
275 322107 : if (starts_with(str, "1.#QNAN") || starts_with(str, "1.#SNAN"))
276 : {
277 2 : if (endptr)
278 2 : *endptr = const_cast<char *>(str.data()) + str.size();
279 2 : return std::numeric_limits<double>::quiet_NaN();
280 : }
281 322105 : if (starts_with_ci(str, "1.#INF"))
282 : {
283 3 : if (endptr)
284 1 : *endptr = const_cast<char *>(str.data()) + str.size();
285 3 : return std::numeric_limits<double>::infinity();
286 : }
287 : }
288 :
289 : // Skip leading '+' as non-handled by fast_float
290 1717710 : if (str[0] == '+')
291 4154 : str = str.substr(1);
292 :
293 : // Find a reasonable position for the end of the string to provide to
294 : // fast_float
295 1717710 : std::ptrdiff_t endPos = 0;
296 31374400 : while (endPos < static_cast<std::ptrdiff_t>(str.size()) &&
297 14913800 : ((str[endPos] >= '0' && str[endPos] <= '9') ||
298 1737980 : str[endPos] == point || str[endPos] == '+' || str[endPos] == '-' ||
299 275764 : str[endPos] == 'e' || str[endPos] == 'E'))
300 : {
301 14742900 : ++endPos;
302 : }
303 :
304 1717710 : double dfValue = 0;
305 : const fast_float::parse_options options{fast_float::chars_format::general,
306 1717710 : point};
307 : auto answer = fast_float::from_chars_advanced(
308 1717710 : str.data(), str.data() + endPos, dfValue, options);
309 1717710 : if (answer.ec != std::errc())
310 : {
311 43699 : if (
312 : // Triggered by ogr_pg tests
313 43699 : starts_with_ci(str, "-Infinity"))
314 : {
315 13 : dfValue = -std::numeric_limits<double>::infinity();
316 13 : answer.ptr = str.data() + strlen("-Infinity");
317 : }
318 43686 : else if (starts_with_ci(str, "-inf"))
319 : {
320 68 : dfValue = -std::numeric_limits<double>::infinity();
321 68 : answer.ptr = str.data() + strlen("-inf");
322 : }
323 43618 : else if (
324 : // Triggered by ogr_pg tests
325 43618 : starts_with_ci(str, "Infinity"))
326 : {
327 13 : dfValue = std::numeric_limits<double>::infinity();
328 13 : answer.ptr = str.data() + strlen("Infinity");
329 : }
330 43605 : else if (cpl::starts_with_ci(str, "inf"))
331 : {
332 136 : dfValue = std::numeric_limits<double>::infinity();
333 136 : answer.ptr = str.data() + strlen("inf");
334 : }
335 43469 : else if (cpl::starts_with_ci(str, "nan"))
336 : {
337 246 : dfValue = std::numeric_limits<double>::quiet_NaN();
338 246 : answer.ptr = str.data() + strlen("nan");
339 : }
340 : else
341 : {
342 43223 : errno = answer.ptr == str.data() ? 0 : ERANGE;
343 : }
344 : }
345 1717710 : if (endptr)
346 : {
347 497077 : *endptr = const_cast<char *>(answer.ptr);
348 : }
349 :
350 1717710 : return dfValue;
351 : }
352 : } // namespace cpl
353 :
354 : /************************************************************************/
355 : /* CPLStrtod() */
356 : /************************************************************************/
357 :
358 : /**
359 : * Converts ASCII string to floating point number.
360 : *
361 : * This function converts the initial portion of the string pointed to
362 : * by nptr to double floating point representation. This function does the
363 : * same as standard strtod(3), but does not take locale in account. That
364 : * means, the decimal delimiter is always '.' (decimal point). Use
365 : * CPLStrtodDelim() function if you want to specify custom delimiter. Also
366 : * see notes for CPLAtof() function.
367 : *
368 : * @param nptr Pointer to string to convert.
369 : * @param endptr If is not NULL, a pointer to the character after the last
370 : * character used in the conversion is stored in the location referenced
371 : * by endptr.
372 : *
373 : * @return Converted value, if any.
374 : */
375 1516020 : double CPLStrtod(const char *nptr, char **endptr)
376 : {
377 1516020 : return CPLStrtodDelim(nptr, endptr, '.');
378 : }
379 :
380 : /************************************************************************/
381 : /* CPLStrtodM() */
382 : /************************************************************************/
383 :
384 : /**
385 : * Converts ASCII string to floating point number.
386 : *
387 : * This function converts the initial portion of the string pointed to
388 : * by nptr to double floating point representation. This function does the
389 : * same as standard strtod(3), but does not take locale in account.
390 : *
391 : * That function accepts '.' (decimal point) or ',' (comma) as decimal
392 : * delimiter.
393 : *
394 : * @param nptr Pointer to string to convert.
395 : * @param endptr If is not NULL, a pointer to the character after the last
396 : * character used in the conversion is stored in the location referenced
397 : * by endptr.
398 : *
399 : * @return Converted value, if any.
400 : * @since GDAL 3.9
401 : */
402 16632 : double CPLStrtodM(const char *nptr, char **endptr)
403 :
404 : {
405 16632 : const int nMaxSearch = 50;
406 :
407 83671 : for (int i = 0; i < nMaxSearch; i++)
408 : {
409 83671 : if (nptr[i] == ',')
410 0 : return CPLStrtodDelim(nptr, endptr, ',');
411 83671 : if (nptr[i] == '.' || nptr[i] == '\0')
412 16632 : return CPLStrtodDelim(nptr, endptr, '.');
413 : }
414 :
415 0 : return CPLStrtodDelim(nptr, endptr, '.');
416 : }
417 :
418 : /************************************************************************/
419 : /* CPLStrtofDelim() */
420 : /************************************************************************/
421 :
422 : /**
423 : * Converts ASCII string to floating point number using specified delimiter.
424 : *
425 : * This function converts the initial portion of the string pointed to
426 : * by nptr to single floating point representation. This function does the
427 : * same as standard strtof(3), but does not take locale in account. Instead of
428 : * locale defined decimal delimiter you can specify your own one. Also see
429 : * notes for CPLAtof() function.
430 : *
431 : * @param nptr Pointer to string to convert.
432 : * @param endptr If is not NULL, a pointer to the character after the last
433 : * character used in the conversion is stored in the location referenced
434 : * by endptr.
435 : * @param point Decimal delimiter.
436 : *
437 : * @return Converted value, if any.
438 : */
439 10 : float CPLStrtofDelim(const char *nptr, char **endptr, char point)
440 : {
441 : /* -------------------------------------------------------------------- */
442 : /* We are implementing a simple method here: copy the input string */
443 : /* into the temporary buffer, replace the specified decimal delimiter */
444 : /* with the one, taken from locale settings and use standard strtof() */
445 : /* on that buffer. */
446 : /* -------------------------------------------------------------------- */
447 10 : char *const pszNewNumberOrNull = CPLReplacePointByLocalePoint(nptr, point);
448 10 : const char *pszNumber = pszNewNumberOrNull ? pszNewNumberOrNull : nptr;
449 10 : const float fValue = strtof(pszNumber, endptr);
450 10 : const int nError = errno;
451 :
452 10 : if (endptr)
453 10 : *endptr = const_cast<char *>(nptr) + (*endptr - pszNumber);
454 :
455 10 : if (pszNewNumberOrNull)
456 0 : CPLFree(pszNewNumberOrNull);
457 :
458 10 : errno = nError;
459 10 : return fValue;
460 : }
461 :
462 : /************************************************************************/
463 : /* CPLStrtof() */
464 : /************************************************************************/
465 :
466 : /**
467 : * Converts ASCII string to floating point number.
468 : *
469 : * This function converts the initial portion of the string pointed to
470 : * by nptr to single floating point representation. This function does the
471 : * same as standard strtof(3), but does not take locale in account. That
472 : * means, the decimal delimiter is always '.' (decimal point). Use
473 : * CPLStrtofDelim() function if you want to specify custom delimiter. Also
474 : * see notes for CPLAtof() function.
475 : *
476 : * @param nptr Pointer to string to convert.
477 : * @param endptr If is not NULL, a pointer to the character after the last
478 : * character used in the conversion is stored in the location referenced
479 : * by endptr.
480 : *
481 : * @return Converted value, if any.
482 : */
483 10 : float CPLStrtof(const char *nptr, char **endptr)
484 : {
485 10 : return CPLStrtofDelim(nptr, endptr, '.');
486 : }
|