1 /* This file is part of Libspectre.
2  *
3  * Copyright (C) 2007, 2012 Albert Astals Cid <aacid@kde.org>
4  * Copyright (C) 2007 Carlos Garcia Campos <carlosgc@gnome.org>
5  *
6  * Libspectre is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2, or (at your option)
9  * any later version.
10  *
11  * Libspectre is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 
21 /* This function comes from spectre-utils from libspectre */
22 
23 #include "gstrtod.h"
24 
25 #include <clocale>
26 #include <cerrno>
27 #include <cstdlib>
28 #include <cstring>
29 
30 #define ascii_isspace(c) (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v')
31 #define ascii_isdigit(c) (c >= '0' && c <= '9')
32 
gatof(const char * nptr)33 double gatof(const char *nptr)
34 {
35     return gstrtod(nptr, nullptr);
36 }
37 
gstrtod(const char * nptr,char ** endptr)38 double gstrtod(const char *nptr, char **endptr)
39 {
40     char *fail_pos;
41     double val;
42     struct lconv *locale_data;
43     const char *decimal_point;
44     int decimal_point_len;
45     const char *p, *decimal_point_pos;
46     const char *end = nullptr; /* Silence gcc */
47     int strtod_errno;
48 
49     fail_pos = nullptr;
50 
51     locale_data = localeconv();
52     decimal_point = locale_data->decimal_point;
53     decimal_point_len = strlen(decimal_point);
54 
55     decimal_point_pos = nullptr;
56     end = nullptr;
57 
58     if (decimal_point[0] != '.' || decimal_point[1] != 0) {
59         p = nptr;
60         /* Skip leading space */
61         while (ascii_isspace(*p))
62             p++;
63 
64         /* Skip leading optional sign */
65         if (*p == '+' || *p == '-')
66             p++;
67 
68         if (ascii_isdigit(*p) || *p == '.') {
69             while (ascii_isdigit(*p))
70                 p++;
71 
72             if (*p == '.')
73                 decimal_point_pos = p++;
74 
75             while (ascii_isdigit(*p))
76                 p++;
77 
78             if (*p == 'e' || *p == 'E')
79                 p++;
80             if (*p == '+' || *p == '-')
81                 p++;
82             while (ascii_isdigit(*p))
83                 p++;
84 
85             end = p;
86         }
87         /* For the other cases, we need not convert the decimal point */
88     }
89 
90     if (decimal_point_pos) {
91         char *copy, *c;
92 
93         /* We need to convert the '.' to the locale specific decimal point */
94         copy = (char *)malloc(end - nptr + 1 + decimal_point_len);
95 
96         c = copy;
97         memcpy(c, nptr, decimal_point_pos - nptr);
98         c += decimal_point_pos - nptr;
99         memcpy(c, decimal_point, decimal_point_len);
100         c += decimal_point_len;
101         memcpy(c, decimal_point_pos + 1, end - (decimal_point_pos + 1));
102         c += end - (decimal_point_pos + 1);
103         *c = 0;
104 
105         errno = 0;
106         val = strtod(copy, &fail_pos);
107         strtod_errno = errno;
108 
109         if (fail_pos) {
110             if (fail_pos - copy > decimal_point_pos - nptr)
111                 fail_pos = (char *)nptr + (fail_pos - copy) - (decimal_point_len - 1);
112             else
113                 fail_pos = (char *)nptr + (fail_pos - copy);
114         }
115 
116         free(copy);
117     } else if (end) {
118         char *copy;
119 
120         copy = (char *)malloc(end - (char *)nptr + 1);
121         memcpy(copy, nptr, end - nptr);
122         *(copy + (end - (char *)nptr)) = 0;
123 
124         errno = 0;
125         val = strtod(copy, &fail_pos);
126         strtod_errno = errno;
127 
128         if (fail_pos) {
129             fail_pos = (char *)nptr + (fail_pos - copy);
130         }
131 
132         free(copy);
133     } else {
134         errno = 0;
135         val = strtod(nptr, &fail_pos);
136         strtod_errno = errno;
137     }
138 
139     if (endptr)
140         *endptr = fail_pos;
141 
142     errno = strtod_errno;
143 
144     return val;
145 }
146