Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mpx/lua-cjson.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Pulford <mark@kyne.com.au>2011-12-30 07:47:44 +0400
committerMark Pulford <mark@kyne.com.au>2011-12-30 07:47:44 +0400
commit2416b145073211b840781da6abf4b6d97f4657a6 (patch)
tree6e92a13a7cc8ef8357245bc3ef320f5841350991 /fpconv.c
parent6cc88e3ac5275868e168acaf60203563f131355b (diff)
Add fpconv to work around comma decimal points
Create a separate buffer and translate comma <> dot before calling strtod(), and after calling sprintf() as required. - Add "update_locale" Lua API call and init locale on module load. - Move sprintf format string to fpconv
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 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include "fpconv.h"
+
+static char locale_decimal_point = '.';
+
+/* In theory multibyte decimal_points are possible, but
+ * Lua CJSON only supports UTF-8 and known locales only have
+ * single byte decimal points ([.,]).
+ *
+ * localconv() may not be thread safe, and nl_langinfo() is not
+ * supported on some platforms. Use sprintf() instead. */
+void fpconv_update_locale()
+{
+ char buf[8];
+
+ snprintf(buf, sizeof(buf), "%g", 0.5);
+
+ /* Failing this test might imply the platform has a buggy dtoa
+ * implementation or wide characters */
+ if (buf[0] != '0' || buf[2] != '5' || buf[3] != 0) {
+ fprintf(stderr, "Error: wide characters found or printf() bug.");
+ abort();
+ }
+
+ locale_decimal_point = buf[1];
+}
+
+/* Check for a valid number character: [-+0-9a-fA-FpPxX.]
+ * It doesn't matter if actual invalid characters are counted - strtod()
+ * will find the valid number if it exists. The risk is that slightly more
+ * memory might be allocated before a parse error occurs. */
+static int valid_number_character(char ch)
+{
+ char lower_ch;
+
+ if ('0' <= ch && ch <= '9')
+ return 1;
+ if (ch == '-' || ch == '+' || ch == '.')
+ return 1;
+
+ /* Hex digits, exponent (e), base (p), "infinity",..
+ * The main purpose is to not include a "comma". If any other invalid
+ * characters are included, the will only generate a parse error later. */
+ lower_ch = ch | 0x20;
+ if ('a' <= lower_ch && lower_ch <= 'y')
+ return 1;
+
+ return 0;
+}
+
+/* Calculate the size of the buffer required for a locale
+ * conversion. Returns 0 if conversion is not required */
+static int strtod_buffer_size(const char *s)
+{
+ const char *p = s;
+
+ while (valid_number_character(*p))
+ p++;
+
+ return p - s;
+}
+
+/* Similar to strtod(), but must be passed the current locale's decimal point
+ * character. Guaranteed to be called at the start of any valid number in a string */
+double fpconv_strtod(const char *nptr, char **endptr)
+{
+ char *num, *endnum, *dp;
+ int numlen;
+ double value;
+
+ /* System strtod() is fine when decimal point is '.' */
+ if (locale_decimal_point == '.')
+ return strtod(nptr, endptr);
+
+ numlen = strtod_buffer_size(nptr);
+ if (!numlen) {
+ /* No valid characters found, standard strtod() return */
+ *endptr = (char *)nptr;
+ return 0;
+ }
+
+ /* Duplicate number into buffer */
+ num = malloc(numlen + 1);
+ if (!num) {
+ fprintf(stderr, "Out of memory");
+ abort();
+ }
+ memcpy(num, nptr, numlen);
+ num[numlen] = 0;
+
+ /* Update decimal point character if found */
+ dp = strchr(num, '.');
+ if (dp)
+ *dp = locale_decimal_point;
+
+ value = strtod(num, &endnum);
+ *endptr = (char *)&nptr[endnum - num];
+ free(num);
+
+ return value;
+}
+
+/* "fmt" must point to a buffer of at least 6 characters */
+static void set_number_format(char *fmt, int precision)
+{
+ int d1, d2, i;
+
+ assert(1 <= precision && precision <= 14);
+
+ /* Create printf format (%.14g) from precision */
+ d1 = precision / 10;
+ d2 = precision % 10;
+ fmt[0] = '%';
+ fmt[1] = '.';
+ i = 2;
+ if (d1) {
+ fmt[i++] = '0' + d1;
+ }
+ fmt[i++] = '0' + d2;
+ fmt[i++] = 'g';
+ fmt[i++] = 0;
+}
+
+/* Assumes there is always at least 32 characters available in the target buffer */
+int fpconv_g_fmt(char *str, double num, int precision)
+{
+ char buf[FPCONV_G_FMT_BUFSIZE];
+ char fmt[6];
+ int len;
+ char *b;
+
+ set_number_format(fmt, precision);
+
+ /* Pass through when decimal point character is dot. */
+ if (locale_decimal_point == '.')
+ return snprintf(str, FPCONV_G_FMT_BUFSIZE, fmt, num);
+
+ /* snprintf() to a buffer then translate for other decimal point characters */
+ len = snprintf(buf, FPCONV_G_FMT_BUFSIZE, fmt, num);
+
+ /* Returned 'len' includes the null terminator */
+ b = buf;
+ do {
+ *str++ = (*b == locale_decimal_point ? '.' : *b);
+ } while(*b++);
+
+ return len;
+}
+
+/* vi:ai et sw=4 ts=4:
+ */