xref: /redis-3.2.3/deps/lua/src/fpconv.c (revision 4fdcd213)
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. */
fpconv_update_locale()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. */
valid_number_character(char ch)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. */
strtod_buffer_size(const char * s)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 */
fpconv_strtod(const char * nptr,char ** endptr)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 */
set_number_format(char * fmt,int precision)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 */
fpconv_g_fmt(char * str,double num,int precision)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 
fpconv_init()199 void fpconv_init()
200 {
201     fpconv_update_locale();
202 }
203 
204 /* vi:ai et sw=4 ts=4:
205  */
206