neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

fpconv.c (6173B)


      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 
     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 /* Workaround for MSVC */
     39 #ifdef _MSC_VER
     40 #define inline __inline
     41 #define snprintf sprintf_s
     42 #endif
     43 
     44 /* Lua CJSON assumes the locale is the same for all threads within a
     45 * process and doesn't change after initialisation.
     46 *
     47 * This avoids the need for per thread storage or expensive checks
     48 * for call. */
     49 static char locale_decimal_point = '.';
     50 
     51 /* In theory multibyte decimal_points are possible, but
     52 * Lua CJSON only supports UTF-8 and known locales only have
     53 * single byte decimal points ([.,]).
     54 *
     55 * localconv() may not be thread safe (=>crash), and nl_langinfo() is
     56 * not supported on some platforms. Use sprintf() instead - if the
     57 * locale does change, at least Lua CJSON won't crash. */
     58 static void fpconv_update_locale(void)
     59 {
     60    char buf[8];
     61 
     62    snprintf(buf, sizeof(buf), "%g", 0.5);
     63 
     64    /* Failing this test might imply the platform has a buggy dtoa
     65     * implementation or wide characters */
     66    if (buf[0] != '0' || buf[2] != '5' || buf[3] != 0) {
     67        fprintf(stderr, "Error: wide characters found or printf() bug.");
     68        abort();
     69    }
     70 
     71    locale_decimal_point = buf[1];
     72 }
     73 
     74 /* Check for a valid number character: [-+0-9a-yA-Y.]
     75 * Eg: -0.6e+5, infinity, 0xF0.F0pF0
     76 *
     77 * Used to find the probable end of a number. It doesn't matter if
     78 * invalid characters are counted - strtod() will find the valid
     79 * number if it exists.  The risk is that slightly more memory might
     80 * be allocated before a parse error occurs. */
     81 static inline int valid_number_character(char ch)
     82 {
     83    char lower_ch;
     84 
     85    if ('0' <= ch && ch <= '9')
     86        return 1;
     87    if (ch == '-' || ch == '+' || ch == '.')
     88        return 1;
     89 
     90    /* Hex digits, exponent (e), base (p), "infinity",.. */
     91    lower_ch = ch | 0x20;
     92    if ('a' <= lower_ch && lower_ch <= 'y')
     93        return 1;
     94 
     95    return 0;
     96 }
     97 
     98 /* Calculate the size of the buffer required for a strtod locale
     99 * conversion. */
    100 static int strtod_buffer_size(const char *s)
    101 {
    102    const char *p = s;
    103 
    104    while (valid_number_character(*p))
    105        p++;
    106 
    107    return p - s;
    108 }
    109 
    110 /* Similar to strtod(), but must be passed the current locale's decimal point
    111 * character. Guaranteed to be called at the start of any valid number in a string */
    112 double fpconv_strtod(const char *nptr, char **endptr)
    113 {
    114    char localbuf[FPCONV_G_FMT_BUFSIZE];
    115    char *buf, *endbuf, *dp;
    116    int buflen;
    117    double value;
    118 
    119    /* System strtod() is fine when decimal point is '.' */
    120    if (locale_decimal_point == '.')
    121        return strtod(nptr, endptr);
    122 
    123    buflen = strtod_buffer_size(nptr);
    124    if (!buflen) {
    125        /* No valid characters found, standard strtod() return */
    126        *endptr = (char *)nptr;
    127        return 0;
    128    }
    129 
    130    /* Duplicate number into buffer */
    131    if (buflen >= FPCONV_G_FMT_BUFSIZE) {
    132        /* Handle unusually large numbers */
    133        buf = (char *)malloc(buflen + 1);
    134        if (!buf) {
    135            fprintf(stderr, "Out of memory");
    136            abort();
    137        }
    138    } else {
    139        /* This is the common case.. */
    140        buf = localbuf;
    141    }
    142    memcpy(buf, nptr, buflen);
    143    buf[buflen] = 0;
    144 
    145    /* Update decimal point character if found */
    146    dp = strchr(buf, '.');
    147    if (dp)
    148        *dp = locale_decimal_point;
    149 
    150    value = strtod(buf, &endbuf);
    151    *endptr = (char *)&nptr[endbuf - buf];
    152    if (buflen >= FPCONV_G_FMT_BUFSIZE)
    153        free(buf);
    154 
    155    return value;
    156 }
    157 
    158 /* "fmt" must point to a buffer of at least 6 characters */
    159 static void set_number_format(char *fmt, int precision)
    160 {
    161    int d1, d2, i;
    162 
    163    assert(1 <= precision && precision <= 16);
    164 
    165    /* Create printf format (%.14g) from precision */
    166    d1 = precision / 10;
    167    d2 = precision % 10;
    168    fmt[0] = '%';
    169    fmt[1] = '.';
    170    i = 2;
    171    if (d1) {
    172        fmt[i++] = '0' + d1;
    173    }
    174    fmt[i++] = '0' + d2;
    175    fmt[i++] = 'g';
    176    fmt[i] = 0;
    177 }
    178 
    179 /* Assumes there is always at least 32 characters available in the target buffer */
    180 int fpconv_g_fmt(char *str, double num, int precision)
    181 {
    182    char buf[FPCONV_G_FMT_BUFSIZE];
    183    char fmt[6];
    184    int len;
    185    char *b;
    186 
    187    set_number_format(fmt, precision);
    188 
    189    /* Pass through when decimal point character is dot. */
    190    if (locale_decimal_point == '.')
    191        return snprintf(str, FPCONV_G_FMT_BUFSIZE, fmt, num);
    192 
    193    /* snprintf() to a buffer then translate for other decimal point characters */
    194    len = snprintf(buf, FPCONV_G_FMT_BUFSIZE, fmt, num);
    195 
    196    /* Copy into target location. Translate decimal point if required */
    197    b = buf;
    198    do {
    199        *str++ = (*b == locale_decimal_point ? '.' : *b);
    200    } while(*b++);
    201 
    202    return len;
    203 }
    204 
    205 void fpconv_init(void)
    206 {
    207    fpconv_update_locale();
    208 }
    209 
    210 /* vi:ai et sw=4 ts=4:
    211 */