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