1 /* fpconv - Floating point conversion routines 2 * 3 * Copyright (c) 2011-2012 Mark Pulford <[email protected]> 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining 6 * a copy of this software and associated documentation files (the 7 * "Software"), to deal in the Software without restriction, including 8 * without limitation the rights to use, copy, modify, merge, publish, 9 * distribute, sublicense, and/or sell copies of the Software, and to 10 * permit persons to whom the Software is furnished to do so, subject to 11 * the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be 14 * included in all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 */ 24 25 /* JSON uses a '.' decimal separator. strtod() / sprintf() under C libraries 26 * with locale support will break when the decimal separator is a comma. 27 * 28 * fpconv_* will around these issues with a translation buffer if required. 29 */ 30 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <assert.h> 34 #include <string.h> 35 36 #include "fpconv.h" 37 38 /* Lua CJSON assumes the locale is the same for all threads within a 39 * process and doesn't change after initialisation. 40 * 41 * This avoids the need for per thread storage or expensive checks 42 * for call. */ 43 static char locale_decimal_point = '.'; 44 45 /* In theory multibyte decimal_points are possible, but 46 * Lua CJSON only supports UTF-8 and known locales only have 47 * single byte decimal points ([.,]). 48 * 49 * localconv() may not be thread safe (=>crash), and nl_langinfo() is 50 * not supported on some platforms. Use sprintf() instead - if the 51 * locale does change, at least Lua CJSON won't crash. */ 52 static void fpconv_update_locale() 53 { 54 char buf[8]; 55 56 snprintf(buf, sizeof(buf), "%g", 0.5); 57 58 /* Failing this test might imply the platform has a buggy dtoa 59 * implementation or wide characters */ 60 if (buf[0] != '0' || buf[2] != '5' || buf[3] != 0) { 61 fprintf(stderr, "Error: wide characters found or printf() bug."); 62 abort(); 63 } 64 65 locale_decimal_point = buf[1]; 66 } 67 68 /* Check for a valid number character: [-+0-9a-yA-Y.] 69 * Eg: -0.6e+5, infinity, 0xF0.F0pF0 70 * 71 * Used to find the probable end of a number. It doesn't matter if 72 * invalid characters are counted - strtod() will find the valid 73 * number if it exists. The risk is that slightly more memory might 74 * be allocated before a parse error occurs. */ 75 static inline int valid_number_character(char ch) 76 { 77 char lower_ch; 78 79 if ('0' <= ch && ch <= '9') 80 return 1; 81 if (ch == '-' || ch == '+' || ch == '.') 82 return 1; 83 84 /* Hex digits, exponent (e), base (p), "infinity",.. */ 85 lower_ch = ch | 0x20; 86 if ('a' <= lower_ch && lower_ch <= 'y') 87 return 1; 88 89 return 0; 90 } 91 92 /* Calculate the size of the buffer required for a strtod locale 93 * conversion. */ 94 static int strtod_buffer_size(const char *s) 95 { 96 const char *p = s; 97 98 while (valid_number_character(*p)) 99 p++; 100 101 return p - s; 102 } 103 104 /* Similar to strtod(), but must be passed the current locale's decimal point 105 * character. Guaranteed to be called at the start of any valid number in a string */ 106 double fpconv_strtod(const char *nptr, char **endptr) 107 { 108 char localbuf[FPCONV_G_FMT_BUFSIZE]; 109 char *buf, *endbuf, *dp; 110 int buflen; 111 double value; 112 113 /* System strtod() is fine when decimal point is '.' */ 114 if (locale_decimal_point == '.') 115 return strtod(nptr, endptr); 116 117 buflen = strtod_buffer_size(nptr); 118 if (!buflen) { 119 /* No valid characters found, standard strtod() return */ 120 *endptr = (char *)nptr; 121 return 0; 122 } 123 124 /* Duplicate number into buffer */ 125 if (buflen >= FPCONV_G_FMT_BUFSIZE) { 126 /* Handle unusually large numbers */ 127 buf = malloc(buflen + 1); 128 if (!buf) { 129 fprintf(stderr, "Out of memory"); 130 abort(); 131 } 132 } else { 133 /* This is the common case.. */ 134 buf = localbuf; 135 } 136 memcpy(buf, nptr, buflen); 137 buf[buflen] = 0; 138 139 /* Update decimal point character if found */ 140 dp = strchr(buf, '.'); 141 if (dp) 142 *dp = locale_decimal_point; 143 144 value = strtod(buf, &endbuf); 145 *endptr = (char *)&nptr[endbuf - buf]; 146 if (buflen >= FPCONV_G_FMT_BUFSIZE) 147 free(buf); 148 149 return value; 150 } 151 152 /* "fmt" must point to a buffer of at least 6 characters */ 153 static void set_number_format(char *fmt, int precision) 154 { 155 int d1, d2, i; 156 157 assert(1 <= precision && precision <= 14); 158 159 /* Create printf format (%.14g) from precision */ 160 d1 = precision / 10; 161 d2 = precision % 10; 162 fmt[0] = '%'; 163 fmt[1] = '.'; 164 i = 2; 165 if (d1) { 166 fmt[i++] = '0' + d1; 167 } 168 fmt[i++] = '0' + d2; 169 fmt[i++] = 'g'; 170 fmt[i] = 0; 171 } 172 173 /* Assumes there is always at least 32 characters available in the target buffer */ 174 int fpconv_g_fmt(char *str, double num, int precision) 175 { 176 char buf[FPCONV_G_FMT_BUFSIZE]; 177 char fmt[6]; 178 int len; 179 char *b; 180 181 set_number_format(fmt, precision); 182 183 /* Pass through when decimal point character is dot. */ 184 if (locale_decimal_point == '.') 185 return snprintf(str, FPCONV_G_FMT_BUFSIZE, fmt, num); 186 187 /* snprintf() to a buffer then translate for other decimal point characters */ 188 len = snprintf(buf, FPCONV_G_FMT_BUFSIZE, fmt, num); 189 190 /* Copy into target location. Translate decimal point if required */ 191 b = buf; 192 do { 193 *str++ = (*b == locale_decimal_point ? '.' : *b); 194 } while(*b++); 195 196 return len; 197 } 198 199 void fpconv_init() 200 { 201 fpconv_update_locale(); 202 } 203 204 /* vi:ai et sw=4 ts=4: 205 */ 206