/src/jansson/src/strconv.c
Line | Count | Source |
1 | | #include "jansson_private.h" |
2 | | #include "strbuffer.h" |
3 | | #include <assert.h> |
4 | | #include <errno.h> |
5 | | #include <math.h> |
6 | | #include <stdio.h> |
7 | | #include <string.h> |
8 | | |
9 | | /* need jansson_private_config.h to get the correct snprintf */ |
10 | | #ifdef HAVE_CONFIG_H |
11 | | #include <jansson_private_config.h> |
12 | | #endif |
13 | | |
14 | | /* |
15 | | - This code assumes that the decimal separator is exactly one |
16 | | character. |
17 | | |
18 | | - If setlocale() is called by another thread between the call to |
19 | | get_decimal_point() and the call to sprintf() or strtod(), the |
20 | | result may be wrong. setlocale() is not thread-safe and should |
21 | | not be used this way. Multi-threaded programs should use |
22 | | uselocale() instead. |
23 | | */ |
24 | 0 | static char get_decimal_point() { |
25 | 0 | char buf[3]; |
26 | 0 | sprintf(buf, "%#.0f", 1.0); // "1." in the current locale |
27 | 0 | return buf[1]; |
28 | 0 | } |
29 | | |
30 | 0 | static void to_locale(strbuffer_t *strbuffer) { |
31 | 0 | char point; |
32 | 0 | char *pos; |
33 | |
|
34 | 0 | point = get_decimal_point(); |
35 | 0 | if (point == '.') { |
36 | | /* No conversion needed */ |
37 | 0 | return; |
38 | 0 | } |
39 | | |
40 | 0 | pos = strchr(strbuffer->value, '.'); |
41 | 0 | if (pos) |
42 | 0 | *pos = point; |
43 | 0 | } |
44 | | |
45 | 0 | int jsonp_strtod(strbuffer_t *strbuffer, double *out) { |
46 | 0 | double value; |
47 | 0 | char *end; |
48 | |
|
49 | 0 | to_locale(strbuffer); |
50 | |
|
51 | 0 | errno = 0; |
52 | 0 | value = strtod(strbuffer->value, &end); |
53 | 0 | assert(end == strbuffer->value + strbuffer->length); |
54 | |
|
55 | 0 | if ((value == HUGE_VAL || value == -HUGE_VAL) && errno == ERANGE) { |
56 | | /* Overflow */ |
57 | 0 | return -1; |
58 | 0 | } |
59 | | |
60 | 0 | *out = value; |
61 | 0 | return 0; |
62 | 0 | } |
63 | | |
64 | | #if DTOA_ENABLED |
65 | | /* see dtoa.c */ |
66 | | char *dtoa_r(double dd, int mode, int ndigits, int *decpt, int *sign, char **rve, |
67 | | char *buf, size_t blen); |
68 | | |
69 | 0 | int jsonp_dtostr(char *buffer, size_t size, double value, int precision) { |
70 | | /* adapted from `format_float_short()` in |
71 | | * https://github.com/python/cpython/blob/2cf18a44303b6d84faa8ecffaecc427b53ae121e/Python/pystrtod.c#L969 |
72 | | */ |
73 | 0 | char digits[25]; |
74 | 0 | char *digits_end; |
75 | 0 | int mode = precision == 0 ? 0 : 2; |
76 | 0 | int decpt, sign, exp_len, exp = 0, use_exp = 0; |
77 | 0 | int digits_len, vdigits_start, vdigits_end; |
78 | 0 | char *p; |
79 | |
|
80 | 0 | if (dtoa_r(value, mode, precision, &decpt, &sign, &digits_end, digits, 25) == NULL) { |
81 | | // digits is too short => should not happen |
82 | 0 | return -1; |
83 | 0 | } |
84 | | |
85 | 0 | digits_len = digits_end - digits; |
86 | 0 | if (decpt <= -4 || decpt > 16) { |
87 | 0 | use_exp = 1; |
88 | 0 | exp = decpt - 1; |
89 | 0 | decpt = 1; |
90 | 0 | } |
91 | |
|
92 | 0 | vdigits_start = decpt <= 0 ? decpt - 1 : 0; |
93 | 0 | vdigits_end = digits_len; |
94 | 0 | if (!use_exp) { |
95 | | /* decpt + 1 to add ".0" if value is an integer */ |
96 | 0 | vdigits_end = vdigits_end > decpt ? vdigits_end : decpt + 1; |
97 | 0 | } else { |
98 | 0 | vdigits_end = vdigits_end > decpt ? vdigits_end : decpt; |
99 | 0 | } |
100 | |
|
101 | 0 | if ( |
102 | | /* sign, decimal point and trailing 0 byte */ |
103 | 0 | (size_t)(3 + |
104 | | |
105 | | /* total digit count (including zero padding on both sides) */ |
106 | 0 | (vdigits_end - vdigits_start) + |
107 | | |
108 | | /* exponent "e+100", max 3 numerical digits */ |
109 | 0 | (use_exp ? 5 : 0)) > size) { |
110 | | /* buffer is too short */ |
111 | 0 | return -1; |
112 | 0 | } |
113 | | |
114 | 0 | p = buffer; |
115 | 0 | if (sign == 1) { |
116 | 0 | *p++ = '-'; |
117 | 0 | } |
118 | | |
119 | | /* note that exactly one of the three 'if' conditions is true, |
120 | | so we include exactly one decimal point */ |
121 | | /* Zero padding on left of digit string */ |
122 | 0 | if (decpt <= 0) { |
123 | 0 | memset(p, '0', decpt - vdigits_start); |
124 | 0 | p += decpt - vdigits_start; |
125 | 0 | *p++ = '.'; |
126 | 0 | memset(p, '0', 0 - decpt); |
127 | 0 | p += 0 - decpt; |
128 | 0 | } else { |
129 | 0 | memset(p, '0', 0 - vdigits_start); |
130 | 0 | p += 0 - vdigits_start; |
131 | 0 | } |
132 | | |
133 | | /* Digits, with included decimal point */ |
134 | 0 | if (0 < decpt && decpt <= digits_len) { |
135 | 0 | strncpy(p, digits, decpt - 0); |
136 | 0 | p += decpt - 0; |
137 | 0 | *p++ = '.'; |
138 | 0 | strncpy(p, digits + decpt, digits_len - decpt); |
139 | 0 | p += digits_len - decpt; |
140 | 0 | } else { |
141 | 0 | strncpy(p, digits, digits_len); |
142 | 0 | p += digits_len; |
143 | 0 | } |
144 | | |
145 | | /* And zeros on the right */ |
146 | 0 | if (digits_len < decpt) { |
147 | 0 | memset(p, '0', decpt - digits_len); |
148 | 0 | p += decpt - digits_len; |
149 | 0 | *p++ = '.'; |
150 | 0 | memset(p, '0', vdigits_end - decpt); |
151 | 0 | p += vdigits_end - decpt; |
152 | 0 | } else { |
153 | 0 | memset(p, '0', vdigits_end - digits_len); |
154 | 0 | p += vdigits_end - digits_len; |
155 | 0 | } |
156 | |
|
157 | 0 | if (p[-1] == '.') |
158 | 0 | p--; |
159 | |
|
160 | 0 | if (use_exp) { |
161 | 0 | *p++ = 'e'; |
162 | 0 | exp_len = sprintf(p, "%d", exp); |
163 | 0 | p += exp_len; |
164 | 0 | } |
165 | 0 | *p = '\0'; |
166 | |
|
167 | 0 | return (int)(p - buffer); |
168 | 0 | } |
169 | | #else /* DTOA_ENABLED == 0 */ |
170 | | static void from_locale(char *buffer) { |
171 | | char point; |
172 | | char *pos; |
173 | | |
174 | | point = get_decimal_point(); |
175 | | if (point == '.') { |
176 | | /* No conversion needed */ |
177 | | return; |
178 | | } |
179 | | |
180 | | pos = strchr(buffer, point); |
181 | | if (pos) |
182 | | *pos = '.'; |
183 | | } |
184 | | |
185 | | int jsonp_dtostr(char *buffer, size_t size, double value, int precision) { |
186 | | int ret; |
187 | | char *start, *end; |
188 | | size_t length; |
189 | | |
190 | | if (precision == 0) |
191 | | precision = 17; |
192 | | |
193 | | ret = snprintf(buffer, size, "%.*g", precision, value); |
194 | | if (ret < 0) |
195 | | return -1; |
196 | | |
197 | | length = (size_t)ret; |
198 | | if (length >= size) |
199 | | return -1; |
200 | | |
201 | | from_locale(buffer); |
202 | | |
203 | | /* Make sure there's a dot or 'e' in the output. Otherwise |
204 | | a real is converted to an integer when decoding */ |
205 | | if (strchr(buffer, '.') == NULL && strchr(buffer, 'e') == NULL) { |
206 | | if (length + 3 >= size) { |
207 | | /* No space to append ".0" */ |
208 | | return -1; |
209 | | } |
210 | | buffer[length] = '.'; |
211 | | buffer[length + 1] = '0'; |
212 | | buffer[length + 2] = '\0'; |
213 | | length += 2; |
214 | | } |
215 | | |
216 | | /* Remove leading '+' from positive exponent. Also remove leading |
217 | | zeros from exponents (added by some printf() implementations) */ |
218 | | start = strchr(buffer, 'e'); |
219 | | if (start) { |
220 | | start++; |
221 | | end = start + 1; |
222 | | |
223 | | if (*start == '-') |
224 | | start++; |
225 | | |
226 | | while (*end == '0') |
227 | | end++; |
228 | | |
229 | | if (end != start) { |
230 | | memmove(start, end, length - (size_t)(end - buffer)); |
231 | | length -= (size_t)(end - start); |
232 | | } |
233 | | } |
234 | | |
235 | | return (int)length; |
236 | | } |
237 | | #endif |