Coverage Report

Created: 2025-06-13 06:18

/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
0
{
104
0
    return CPLStrtod(nptr, nullptr);
105
0
}
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
0
{
130
0
    const int nMaxSearch = 50;
131
132
0
    for (int i = 0; i < nMaxSearch; i++)
133
0
    {
134
0
        if (nptr[i] == ',')
135
0
            return CPLStrtodDelim(nptr, nullptr, ',');
136
0
        if (nptr[i] == '.' || nptr[i] == '\0')
137
0
            return CPLStrtodDelim(nptr, nullptr, '.');
138
0
    }
139
140
0
    return CPLStrtodDelim(nptr, nullptr, '.');
141
0
}
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
0
{
220
0
    while (*nptr == ' '
221
0
#ifdef USE_FAST_FLOAT
222
           // The GSAG driver provides leading end-of-line character
223
0
           || *nptr == '\r' || *nptr == '\n' || *nptr == '\t'
224
0
#endif
225
0
    )
226
0
    {
227
0
        nptr++;
228
0
    }
229
230
0
    if (nptr[0] == '-')
231
0
    {
232
0
        if (STARTS_WITH(nptr, "-1.#QNAN") || STARTS_WITH(nptr, "-1.#IND"))
233
0
        {
234
0
            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
0
            return std::numeric_limits<double>::quiet_NaN();
240
0
        }
241
0
        if (
242
#ifndef USE_FAST_FLOAT
243
            strcmp(nptr, "-inf") == 0 ||
244
#endif
245
0
            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
0
    }
252
0
    else if (nptr[0] == '1')
253
0
    {
254
0
        if (STARTS_WITH(nptr, "1.#QNAN") || STARTS_WITH(nptr, "1.#SNAN"))
255
0
        {
256
0
            if (endptr)
257
0
                *endptr = const_cast<char *>(nptr) + strlen(nptr);
258
0
            return std::numeric_limits<double>::quiet_NaN();
259
0
        }
260
0
        if (STARTS_WITH_CI(nptr, "1.#INF"))
261
0
        {
262
0
            if (endptr)
263
0
                *endptr = const_cast<char *>(nptr) + strlen(nptr);
264
0
            return std::numeric_limits<double>::infinity();
265
0
        }
266
0
    }
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
0
#ifdef USE_FAST_FLOAT
283
    // Skip leading '+' as non-handled by fast_float
284
0
    if (*nptr == '+')
285
0
        nptr++;
286
287
    // Find a reasonable position for the end of the string to provide to
288
    // fast_float
289
0
    const char *endptrIn = nptr;
290
0
    while ((*endptrIn >= '0' && *endptrIn <= '9') || *endptrIn == point ||
291
0
           *endptrIn == '+' || *endptrIn == '-' || *endptrIn == 'e' ||
292
0
           *endptrIn == 'E')
293
0
    {
294
0
        ++endptrIn;
295
0
    }
296
297
0
    double dfValue = 0;
298
0
    const fast_float::parse_options options{fast_float::chars_format::general,
299
0
                                            point};
300
0
    auto answer =
301
0
        fast_float::from_chars_advanced(nptr, endptrIn, dfValue, options);
302
0
    if (answer.ec != std::errc())
303
0
    {
304
0
        if (
305
            // Triggered by ogr_pg tests
306
0
            STARTS_WITH_CI(nptr, "-Infinity"))
307
0
        {
308
0
            dfValue = -std::numeric_limits<double>::infinity();
309
0
            answer.ptr = nptr + strlen("-Infinity");
310
0
        }
311
0
        else if (STARTS_WITH_CI(nptr, "-inf"))
312
0
        {
313
0
            dfValue = -std::numeric_limits<double>::infinity();
314
0
            answer.ptr = nptr + strlen("-inf");
315
0
        }
316
0
        else if (
317
            // Triggered by ogr_pg tests
318
0
            STARTS_WITH_CI(nptr, "Infinity"))
319
0
        {
320
0
            dfValue = std::numeric_limits<double>::infinity();
321
0
            answer.ptr = nptr + strlen("Infinity");
322
0
        }
323
0
        else if (STARTS_WITH_CI(nptr, "inf"))
324
0
        {
325
0
            dfValue = std::numeric_limits<double>::infinity();
326
0
            answer.ptr = nptr + strlen("inf");
327
0
        }
328
0
        else if (STARTS_WITH_CI(nptr, "nan"))
329
0
        {
330
0
            dfValue = std::numeric_limits<double>::quiet_NaN();
331
0
            answer.ptr = nptr + strlen("nan");
332
0
        }
333
0
        else
334
0
        {
335
0
            errno = answer.ptr == nptr ? 0 : ERANGE;
336
0
        }
337
0
    }
338
0
    if (endptr)
339
0
    {
340
0
        *endptr = const_cast<char *>(answer.ptr);
341
0
    }
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
0
    return dfValue;
365
0
}
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
0
{
390
0
    return CPLStrtodDelim(nptr, endptr, '.');
391
0
}
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
0
{
418
0
    const int nMaxSearch = 50;
419
420
0
    for (int i = 0; i < nMaxSearch; i++)
421
0
    {
422
0
        if (nptr[i] == ',')
423
0
            return CPLStrtodDelim(nptr, endptr, ',');
424
0
        if (nptr[i] == '.' || nptr[i] == '\0')
425
0
            return CPLStrtodDelim(nptr, endptr, '.');
426
0
    }
427
428
0
    return CPLStrtodDelim(nptr, endptr, '.');
429
0
}
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
}