From 3d1c5e19f45cf484774926ba6e2555d1c8e4c39b Mon Sep 17 00:00:00 2001 From: Mark Pulford Date: Sun, 29 May 2011 18:03:22 +0930 Subject: Add support for runtime number precision config Add cjson.encode_number_precision(). Reducing the number precision from 14 to 3 can increase performance up to 50% with number heavy conversions. --- README | 13 +++++++++++++ lua_cjson.c | 43 ++++++++++++++++++++++++++++++++++++++++--- strbuf.c | 27 +++++++++++++++------------ strbuf.h | 4 ++-- tests/numbers.json | 7 +++++++ 5 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 tests/numbers.json diff --git a/README b/README index 3fa7595..b4f5bcd 100644 --- a/README +++ b/README @@ -204,6 +204,19 @@ the application. Eg: a = {}; b = { a }; a[1] = b +Number precision +---------------- + + precision = cjson.encode_number_precision([precision]) + -- "precision" must be between 1 and 14 (inclusive) + +By default CJSON will use up to 14 digits for precision when +converting a number to text. + +Reducing number precision to 3 can improve performance of number +heavy conversions by up to 50%. + + Persistent encoding buffer ------------------------- diff --git a/lua_cjson.c b/lua_cjson.c index 2228fee..4b1915a 100644 --- a/lua_cjson.c +++ b/lua_cjson.c @@ -26,9 +26,10 @@ * - JSON "null" values are represented as lightuserdata since Lua * tables cannot contain "nil". Compare with cjson.null. * - Invalid UTF-8 characters are not detected and will be passed - * untouched. + * untouched. If required, UTF-8 error checking should be done + * outside this library. * - Javascript comments are not part of the JSON spec, and are not - * supported. + * currently supported. * * Note: Decoding is slower than encoding. Lua spends significant * time (30%) managing tables when parsing JSON since it is @@ -94,6 +95,7 @@ typedef struct { char *char2escape[256]; /* Encoding */ #endif strbuf_t encode_buf; + char number_fmt[8]; /* "%.XXg\0" */ int current_depth; int encode_sparse_convert; @@ -103,6 +105,7 @@ typedef struct { int encode_refuse_badnum; int decode_refuse_badnum; int encode_keep_buffer; + int encode_number_precision; } json_config_t; typedef struct { @@ -241,6 +244,33 @@ static int json_cfg_encode_max_depth(lua_State *l) return 1; } +static void json_set_number_precision(json_config_t *cfg, int prec) +{ + cfg->encode_number_precision = prec; + sprintf(cfg->number_fmt, "%%.%dg", prec); +} + +/* Configures number precision when converting doubles to text */ +static int json_cfg_encode_number_precision(lua_State *l) +{ + json_config_t *cfg; + int precision; + + json_verify_arg_count(l, 1); + cfg = json_fetch_config(l); + + if (lua_gettop(l)) { + precision = luaL_checkinteger(l, 1); + luaL_argcheck(l, 1 <= precision && precision <= 14, 1, + "expected integer between 1 and 14"); + json_set_number_precision(cfg, precision); + } + + lua_pushinteger(l, cfg->encode_number_precision); + + return 1; +} + /* Configures JSON encoding buffer persistence */ static int json_cfg_encode_keep_buffer(lua_State *l) { @@ -337,6 +367,7 @@ static void json_create_config(lua_State *l) cfg->encode_refuse_badnum = DEFAULT_ENCODE_REFUSE_BADNUM; cfg->decode_refuse_badnum = DEFAULT_DECODE_REFUSE_BADNUM; cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER; + json_set_number_precision(cfg, 14); /* Decoding init */ @@ -552,7 +583,12 @@ static void json_append_number(lua_State *l, strbuf_t *json, int index, if (cfg->encode_refuse_badnum && (isinf(num) || isnan(num))) json_encode_exception(l, cfg, index, "must not be NaN or Inf"); - strbuf_append_number(json, num); + /* Lowest double printed with %.14g is 21 characters long: + * -1.7976931348623e+308 + * + * Use 32 to include the \0, and a few extra just in case.. + */ + strbuf_append_fmt(json, 32, cfg->number_fmt, num); } static void json_append_object(lua_State *l, json_config_t *cfg, @@ -1227,6 +1263,7 @@ int luaopen_cjson(lua_State *l) { "decode", json_decode }, { "encode_sparse_array", json_cfg_encode_sparse_array }, { "encode_max_depth", json_cfg_encode_max_depth }, + { "encode_number_precision", json_cfg_encode_number_precision }, { "encode_keep_buffer", json_cfg_encode_keep_buffer }, { "refuse_invalid_numbers", json_cfg_refuse_invalid_numbers }, { NULL, NULL } diff --git a/strbuf.c b/strbuf.c index c59b6f6..976925a 100644 --- a/strbuf.c +++ b/strbuf.c @@ -197,25 +197,28 @@ void strbuf_append_string(strbuf_t *s, const char *str) } } -void strbuf_append_number(strbuf_t *s, double number) +/* strbuf_append_fmt() should only be used when an upper bound + * is known for the output string. */ +void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...) { - int len; + va_list arg; + int fmt_len; - /* Lowest double printed with %.14g is 21 characters long: - * -1.7976931348623e+308 - * - * Use 32 to include the \0, and a few extra just in case.. - */ - strbuf_ensure_empty_length(s, 32); + strbuf_ensure_empty_length(s, len); - len = sprintf(s->buf + s->length, "%.14g", number); - if (len < 0) + va_start(arg, fmt); + fmt_len = vsnprintf(s->buf + s->length, len, fmt, arg); + va_end(arg); + + if (fmt_len < 0) die("BUG: Unable to convert number"); /* This should never happen.. */ - s->length += len; + s->length += fmt_len; } -void strbuf_append_fmt(strbuf_t *s, const char *fmt, ...) +/* strbuf_append_fmt_retry() can be used when the there is no known + * upper bound for the output string. */ +void strbuf_append_fmt_retry(strbuf_t *s, const char *fmt, ...) { va_list arg; int fmt_len, try; diff --git a/strbuf.h b/strbuf.h index 90ef844..f856543 100644 --- a/strbuf.h +++ b/strbuf.h @@ -65,10 +65,10 @@ static char *strbuf_string(strbuf_t *s, int *len); static void strbuf_ensure_empty_length(strbuf_t *s, int len); /* Update */ -extern void strbuf_append_fmt(strbuf_t *s, const char *format, ...); +extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...); +extern void strbuf_append_fmt_retry(strbuf_t *s, const char *format, ...); static void strbuf_append_mem(strbuf_t *s, const char *c, int len); extern void strbuf_append_string(strbuf_t *s, const char *str); -extern void strbuf_append_number(strbuf_t *s, double number); static void strbuf_append_char(strbuf_t *s, const char c); static void strbuf_ensure_null(strbuf_t *s); diff --git a/tests/numbers.json b/tests/numbers.json new file mode 100644 index 0000000..ef11a26 --- /dev/null +++ b/tests/numbers.json @@ -0,0 +1,7 @@ +[ 0.110001000000000000000001, + 0.12345678910111213141516, + 0.412454033640, + 2.6651441426902251886502972498731, + 2.718281828459045235360287471352, + 3.141592653589793238462643383279, + 2.14069263277926 ] -- cgit v1.2.3-55-g6feb