aboutsummaryrefslogtreecommitdiff
path: root/fpconv.c
diff options
context:
space:
mode:
Diffstat (limited to 'fpconv.c')
-rw-r--r--fpconv.c173
1 files changed, 173 insertions, 0 deletions
diff --git a/fpconv.c b/fpconv.c
new file mode 100644
index 0000000..3bcef41
--- /dev/null
+++ b/fpconv.c
@@ -0,0 +1,173 @@
1/* JSON uses a '.' decimal separator. strtod() / sprintf() under C libraries
2 * with locale support will break when the decimal separator is a comma.
3 *
4 * fpconv_* will around these issues with a translation buffer if required.
5 */
6
7#include <stdio.h>
8#include <stdlib.h>
9#include <assert.h>
10#include <string.h>
11
12#include "fpconv.h"
13
14/* Lua CJSON assumes the locale is the same for all threads within a
15 * process and doesn't change after initialisation.
16 *
17 * This avoids the need for per thread storage or expensive checks
18 * for call. */
19static char locale_decimal_point = '.';
20
21/* In theory multibyte decimal_points are possible, but
22 * Lua CJSON only supports UTF-8 and known locales only have
23 * single byte decimal points ([.,]).
24 *
25 * localconv() may not be thread safe (=>crash), and nl_langinfo() is
26 * not supported on some platforms. Use sprintf() instead - if the
27 * locale does change, at least Lua CJSON won't crash. */
28static void fpconv_update_locale()
29{
30 char buf[8];
31
32 snprintf(buf, sizeof(buf), "%g", 0.5);
33
34 /* Failing this test might imply the platform has a buggy dtoa
35 * implementation or wide characters */
36 if (buf[0] != '0' || buf[2] != '5' || buf[3] != 0) {
37 fprintf(stderr, "Error: wide characters found or printf() bug.");
38 abort();
39 }
40
41 locale_decimal_point = buf[1];
42}
43
44/* Check for a valid number character: [-+0-9a-yA-Y.]
45 * Eg: -0.6e+5, infinity, 0xF0.F0pF0
46 *
47 * Used to find the probable end of a number. It doesn't matter if
48 * invalid characters are counted - strtod() will find the valid
49 * number if it exists. The risk is that slightly more memory might
50 * be allocated before a parse error occurs. */
51static inline int valid_number_character(char ch)
52{
53 char lower_ch;
54
55 if ('0' <= ch && ch <= '9')
56 return 1;
57 if (ch == '-' || ch == '+' || ch == '.')
58 return 1;
59
60 /* Hex digits, exponent (e), base (p), "infinity",.. */
61 lower_ch = ch | 0x20;
62 if ('a' <= lower_ch && lower_ch <= 'y')
63 return 1;
64
65 return 0;
66}
67
68/* Calculate the size of the buffer required for a strtod locale
69 * conversion. */
70static int strtod_buffer_size(const char *s)
71{
72 const char *p = s;
73
74 while (valid_number_character(*p))
75 p++;
76
77 return p - s;
78}
79
80/* Similar to strtod(), but must be passed the current locale's decimal point
81 * character. Guaranteed to be called at the start of any valid number in a string */
82double fpconv_strtod(const char *nptr, char **endptr)
83{
84 char *buf, *endbuf, *dp;
85 int buflen;
86 double value;
87
88 /* System strtod() is fine when decimal point is '.' */
89 if (locale_decimal_point == '.')
90 return strtod(nptr, endptr);
91
92 buflen = strtod_buffer_size(nptr);
93 if (!buflen) {
94 /* No valid characters found, standard strtod() return */
95 *endptr = (char *)nptr;
96 return 0;
97 }
98
99 /* Duplicate number into buffer */
100 buf = malloc(buflen + 1);
101 if (!buf) {
102 fprintf(stderr, "Out of memory");
103 abort();
104 }
105 memcpy(buf, nptr, buflen);
106 buf[buflen] = 0;
107
108 /* Update decimal point character if found */
109 dp = strchr(buf, '.');
110 if (dp)
111 *dp = locale_decimal_point;
112
113 value = strtod(buf, &endbuf);
114 *endptr = (char *)&nptr[endbuf - buf];
115 free(buf);
116
117 return value;
118}
119
120/* "fmt" must point to a buffer of at least 6 characters */
121static void set_number_format(char *fmt, int precision)
122{
123 int d1, d2, i;
124
125 assert(1 <= precision && precision <= 14);
126
127 /* Create printf format (%.14g) from precision */
128 d1 = precision / 10;
129 d2 = precision % 10;
130 fmt[0] = '%';
131 fmt[1] = '.';
132 i = 2;
133 if (d1) {
134 fmt[i++] = '0' + d1;
135 }
136 fmt[i++] = '0' + d2;
137 fmt[i++] = 'g';
138 fmt[i] = 0;
139}
140
141/* Assumes there is always at least 32 characters available in the target buffer */
142int fpconv_g_fmt(char *str, double num, int precision)
143{
144 char buf[FPCONV_G_FMT_BUFSIZE];
145 char fmt[6];
146 int len;
147 char *b;
148
149 set_number_format(fmt, precision);
150
151 /* Pass through when decimal point character is dot. */
152 if (locale_decimal_point == '.')
153 return snprintf(str, FPCONV_G_FMT_BUFSIZE, fmt, num);
154
155 /* snprintf() to a buffer then translate for other decimal point characters */
156 len = snprintf(buf, FPCONV_G_FMT_BUFSIZE, fmt, num);
157
158 /* Copy into target location. Translate decimal point if required */
159 b = buf;
160 do {
161 *str++ = (*b == locale_decimal_point ? '.' : *b);
162 } while(*b++);
163
164 return len;
165}
166
167void fpconv_init()
168{
169 fpconv_update_locale();
170}
171
172/* vi:ai et sw=4 ts=4:
173 */