diff options
| author | Mark Pulford <mark@kyne.com.au> | 2011-12-30 14:17:44 +1030 | 
|---|---|---|
| committer | Mark Pulford <mark@kyne.com.au> | 2011-12-30 14:17:44 +1030 | 
| commit | 2416b145073211b840781da6abf4b6d97f4657a6 (patch) | |
| tree | 6e92a13a7cc8ef8357245bc3ef320f5841350991 /fpconv.c | |
| parent | 6cc88e3ac5275868e168acaf60203563f131355b (diff) | |
| download | lua-cjson-2416b145073211b840781da6abf4b6d97f4657a6.tar.gz lua-cjson-2416b145073211b840781da6abf4b6d97f4657a6.tar.bz2 lua-cjson-2416b145073211b840781da6abf4b6d97f4657a6.zip | |
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 '')
| -rw-r--r-- | fpconv.c | 155 | 
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 | |||
| 8 | static 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. */ | ||
| 16 | void 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. */ | ||
| 36 | static 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 */ | ||
| 57 | static 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 */ | ||
| 69 | double 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 */ | ||
| 108 | static 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 */ | ||
| 129 | int 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 | */ | ||
