fpconv.c 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. /* fpconv - Floating point conversion routines
  2. *
  3. * Copyright (c) 2011-2012 Mark Pulford <mark@kyne.com.au>
  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. /* JSON uses a '.' decimal separator. strtod() / sprintf() under C libraries
  25. * with locale support will break when the decimal separator is a comma.
  26. *
  27. * fpconv_* will around these issues with a translation buffer if required.
  28. */
  29. #include "c_stdio.h"
  30. #include "c_stdlib.h"
  31. // #include <assert.h>
  32. #include "c_string.h"
  33. #include "fpconv.h"
  34. #if 0
  35. /* Lua CJSON assumes the locale is the same for all threads within a
  36. * process and doesn't change after initialisation.
  37. *
  38. * This avoids the need for per thread storage or expensive checks
  39. * for call. */
  40. static char locale_decimal_point = '.';
  41. /* In theory multibyte decimal_points are possible, but
  42. * Lua CJSON only supports UTF-8 and known locales only have
  43. * single byte decimal points ([.,]).
  44. *
  45. * localconv() may not be thread safe (=>crash), and nl_langinfo() is
  46. * not supported on some platforms. Use sprintf() instead - if the
  47. * locale does change, at least Lua CJSON won't crash. */
  48. static void fpconv_update_locale()
  49. {
  50. char buf[8];
  51. c_sprintf(buf, "%g", 0.5);
  52. /* Failing this test might imply the platform has a buggy dtoa
  53. * implementation or wide characters */
  54. if (buf[0] != '0' || buf[2] != '5' || buf[3] != 0) {
  55. NODE_ERR("Error: wide characters found or printf() bug.");
  56. return;
  57. }
  58. locale_decimal_point = buf[1];
  59. }
  60. /* Check for a valid number character: [-+0-9a-yA-Y.]
  61. * Eg: -0.6e+5, infinity, 0xF0.F0pF0
  62. *
  63. * Used to find the probable end of a number. It doesn't matter if
  64. * invalid characters are counted - strtod() will find the valid
  65. * number if it exists. The risk is that slightly more memory might
  66. * be allocated before a parse error occurs. */
  67. static inline int valid_number_character(char ch)
  68. {
  69. char lower_ch;
  70. if ('0' <= ch && ch <= '9')
  71. return 1;
  72. if (ch == '-' || ch == '+' || ch == '.')
  73. return 1;
  74. /* Hex digits, exponent (e), base (p), "infinity",.. */
  75. lower_ch = ch | 0x20;
  76. if ('a' <= lower_ch && lower_ch <= 'y')
  77. return 1;
  78. return 0;
  79. }
  80. /* Calculate the size of the buffer required for a strtod locale
  81. * conversion. */
  82. static int strtod_buffer_size(const char *s)
  83. {
  84. const char *p = s;
  85. while (valid_number_character(*p))
  86. p++;
  87. return p - s;
  88. }
  89. /* Similar to strtod(), but must be passed the current locale's decimal point
  90. * character. Guaranteed to be called at the start of any valid number in a string */
  91. double fpconv_strtod(const char *nptr, char **endptr)
  92. {
  93. char localbuf[FPCONV_G_FMT_BUFSIZE];
  94. char *buf, *endbuf, *dp;
  95. int buflen;
  96. double value;
  97. /* System strtod() is fine when decimal point is '.' */
  98. if (locale_decimal_point == '.')
  99. return c_strtod(nptr, endptr);
  100. buflen = strtod_buffer_size(nptr);
  101. if (!buflen) {
  102. /* No valid characters found, standard strtod() return */
  103. *endptr = (char *)nptr;
  104. return 0;
  105. }
  106. /* Duplicate number into buffer */
  107. if (buflen >= FPCONV_G_FMT_BUFSIZE) {
  108. /* Handle unusually large numbers */
  109. buf = c_malloc(buflen + 1);
  110. if (!buf) {
  111. NODE_ERR("not enough memory\n");
  112. return;
  113. }
  114. } else {
  115. /* This is the common case.. */
  116. buf = localbuf;
  117. }
  118. c_memcpy(buf, nptr, buflen);
  119. buf[buflen] = 0;
  120. /* Update decimal point character if found */
  121. dp = c_strchr(buf, '.');
  122. if (dp)
  123. *dp = locale_decimal_point;
  124. value = c_strtod(buf, &endbuf);
  125. *endptr = (char *)&nptr[endbuf - buf];
  126. if (buflen >= FPCONV_G_FMT_BUFSIZE)
  127. c_free(buf);
  128. return value;
  129. }
  130. /* "fmt" must point to a buffer of at least 6 characters */
  131. static void set_number_format(char *fmt, int precision)
  132. {
  133. int d1, d2, i;
  134. if(!(1 <= precision && precision <= 14)) return;
  135. /* Create printf format (%.14g) from precision */
  136. d1 = precision / 10;
  137. d2 = precision % 10;
  138. fmt[0] = '%';
  139. fmt[1] = '.';
  140. i = 2;
  141. if (d1) {
  142. fmt[i++] = '0' + d1;
  143. }
  144. fmt[i++] = '0' + d2;
  145. fmt[i++] = 'g';
  146. fmt[i] = 0;
  147. }
  148. /* Assumes there is always at least 32 characters available in the target buffer */
  149. int fpconv_g_fmt(char *str, double num, int precision)
  150. {
  151. char buf[FPCONV_G_FMT_BUFSIZE];
  152. char fmt[6];
  153. int len;
  154. char *b;
  155. set_number_format(fmt, precision);
  156. /* Pass through when decimal point character is dot. */
  157. if (locale_decimal_point == '.'){
  158. c_sprintf(str, fmt, num);
  159. return c_strlen(str);
  160. }
  161. /* snprintf() to a buffer then translate for other decimal point characters */
  162. c_sprintf(buf, fmt, num);
  163. len = c_strlen(buf);
  164. /* Copy into target location. Translate decimal point if required */
  165. b = buf;
  166. do {
  167. *str++ = (*b == locale_decimal_point ? '.' : *b);
  168. } while(*b++);
  169. return len;
  170. }
  171. void fpconv_init()
  172. {
  173. fpconv_update_locale();
  174. }
  175. #endif
  176. /* vi:ai et sw=4 ts=4:
  177. */