aboutsummaryrefslogtreecommitdiff
path: root/fpconv.c
diff options
context:
space:
mode:
Diffstat (limited to 'fpconv.c')
-rw-r--r--fpconv.c70
1 files changed, 44 insertions, 26 deletions
diff --git a/fpconv.c b/fpconv.c
index fd4deb0..3bcef41 100644
--- a/fpconv.c
+++ b/fpconv.c
@@ -1,3 +1,9 @@
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
1#include <stdio.h> 7#include <stdio.h>
2#include <stdlib.h> 8#include <stdlib.h>
3#include <assert.h> 9#include <assert.h>
@@ -5,15 +11,21 @@
5 11
6#include "fpconv.h" 12#include "fpconv.h"
7 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. */
8static char locale_decimal_point = '.'; 19static char locale_decimal_point = '.';
9 20
10/* In theory multibyte decimal_points are possible, but 21/* In theory multibyte decimal_points are possible, but
11 * Lua CJSON only supports UTF-8 and known locales only have 22 * Lua CJSON only supports UTF-8 and known locales only have
12 * single byte decimal points ([.,]). 23 * single byte decimal points ([.,]).
13 * 24 *
14 * localconv() may not be thread safe, and nl_langinfo() is not 25 * localconv() may not be thread safe (=>crash), and nl_langinfo() is
15 * supported on some platforms. Use sprintf() instead. */ 26 * not supported on some platforms. Use sprintf() instead - if the
16void fpconv_update_locale() 27 * locale does change, at least Lua CJSON won't crash. */
28static void fpconv_update_locale()
17{ 29{
18 char buf[8]; 30 char buf[8];
19 31
@@ -29,11 +41,14 @@ void fpconv_update_locale()
29 locale_decimal_point = buf[1]; 41 locale_decimal_point = buf[1];
30} 42}
31 43
32/* Check for a valid number character: [-+0-9a-fA-FpPxX.] 44/* Check for a valid number character: [-+0-9a-yA-Y.]
33 * It doesn't matter if actual invalid characters are counted - strtod() 45 * Eg: -0.6e+5, infinity, 0xF0.F0pF0
34 * will find the valid number if it exists. The risk is that slightly more 46 *
35 * memory might be allocated before a parse error occurs. */ 47 * Used to find the probable end of a number. It doesn't matter if
36static int valid_number_character(char ch) 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)
37{ 52{
38 char lower_ch; 53 char lower_ch;
39 54
@@ -42,9 +57,7 @@ static int valid_number_character(char ch)
42 if (ch == '-' || ch == '+' || ch == '.') 57 if (ch == '-' || ch == '+' || ch == '.')
43 return 1; 58 return 1;
44 59
45 /* Hex digits, exponent (e), base (p), "infinity",.. 60 /* 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; 61 lower_ch = ch | 0x20;
49 if ('a' <= lower_ch && lower_ch <= 'y') 62 if ('a' <= lower_ch && lower_ch <= 'y')
50 return 1; 63 return 1;
@@ -52,8 +65,8 @@ static int valid_number_character(char ch)
52 return 0; 65 return 0;
53} 66}
54 67
55/* Calculate the size of the buffer required for a locale 68/* Calculate the size of the buffer required for a strtod locale
56 * conversion. Returns 0 if conversion is not required */ 69 * conversion. */
57static int strtod_buffer_size(const char *s) 70static int strtod_buffer_size(const char *s)
58{ 71{
59 const char *p = s; 72 const char *p = s;
@@ -68,38 +81,38 @@ static int strtod_buffer_size(const char *s)
68 * character. Guaranteed to be called at the start of any valid number in a string */ 81 * character. Guaranteed to be called at the start of any valid number in a string */
69double fpconv_strtod(const char *nptr, char **endptr) 82double fpconv_strtod(const char *nptr, char **endptr)
70{ 83{
71 char *num, *endnum, *dp; 84 char *buf, *endbuf, *dp;
72 int numlen; 85 int buflen;
73 double value; 86 double value;
74 87
75 /* System strtod() is fine when decimal point is '.' */ 88 /* System strtod() is fine when decimal point is '.' */
76 if (locale_decimal_point == '.') 89 if (locale_decimal_point == '.')
77 return strtod(nptr, endptr); 90 return strtod(nptr, endptr);
78 91
79 numlen = strtod_buffer_size(nptr); 92 buflen = strtod_buffer_size(nptr);
80 if (!numlen) { 93 if (!buflen) {
81 /* No valid characters found, standard strtod() return */ 94 /* No valid characters found, standard strtod() return */
82 *endptr = (char *)nptr; 95 *endptr = (char *)nptr;
83 return 0; 96 return 0;
84 } 97 }
85 98
86 /* Duplicate number into buffer */ 99 /* Duplicate number into buffer */
87 num = malloc(numlen + 1); 100 buf = malloc(buflen + 1);
88 if (!num) { 101 if (!buf) {
89 fprintf(stderr, "Out of memory"); 102 fprintf(stderr, "Out of memory");
90 abort(); 103 abort();
91 } 104 }
92 memcpy(num, nptr, numlen); 105 memcpy(buf, nptr, buflen);
93 num[numlen] = 0; 106 buf[buflen] = 0;
94 107
95 /* Update decimal point character if found */ 108 /* Update decimal point character if found */
96 dp = strchr(num, '.'); 109 dp = strchr(buf, '.');
97 if (dp) 110 if (dp)
98 *dp = locale_decimal_point; 111 *dp = locale_decimal_point;
99 112
100 value = strtod(num, &endnum); 113 value = strtod(buf, &endbuf);
101 *endptr = (char *)&nptr[endnum - num]; 114 *endptr = (char *)&nptr[endbuf - buf];
102 free(num); 115 free(buf);
103 116
104 return value; 117 return value;
105} 118}
@@ -142,7 +155,7 @@ int fpconv_g_fmt(char *str, double num, int precision)
142 /* snprintf() to a buffer then translate for other decimal point characters */ 155 /* snprintf() to a buffer then translate for other decimal point characters */
143 len = snprintf(buf, FPCONV_G_FMT_BUFSIZE, fmt, num); 156 len = snprintf(buf, FPCONV_G_FMT_BUFSIZE, fmt, num);
144 157
145 /* Returned 'len' includes the null terminator */ 158 /* Copy into target location. Translate decimal point if required */
146 b = buf; 159 b = buf;
147 do { 160 do {
148 *str++ = (*b == locale_decimal_point ? '.' : *b); 161 *str++ = (*b == locale_decimal_point ? '.' : *b);
@@ -151,5 +164,10 @@ int fpconv_g_fmt(char *str, double num, int precision)
151 return len; 164 return len;
152} 165}
153 166
167void fpconv_init()
168{
169 fpconv_update_locale();
170}
171
154/* vi:ai et sw=4 ts=4: 172/* vi:ai et sw=4 ts=4:
155 */ 173 */