Coverage Report

Created: 2025-06-09 07:42

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