Skip to content

Commit

Permalink
Faster floats
Browse files Browse the repository at this point in the history
  • Loading branch information
ohler55 committed Jul 19, 2020
1 parent ee1fa2e commit 80bee00
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 9 deletions.
7 changes: 6 additions & 1 deletion ext/oj/oj.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ static VALUE custom_sym;
static VALUE empty_string_sym;
static VALUE escape_mode_sym;
static VALUE integer_range_sym;
static VALUE fast_sym;
static VALUE float_prec_sym;
static VALUE float_sym;
static VALUE huge_sym;
Expand Down Expand Up @@ -230,7 +231,7 @@ struct _options oj_default_options = {
* - *:mode* [_:object_|_:strict_|_:compat_|_:null_|_:custom_|_:rails_|_:wab_] load and dump modes to use for JSON
* - *:time_format* [_:unix_|_:unix_zone_|_:xmlschema_|_:ruby_] time format when dumping
* - *:bigdecimal_as_decimal* [_Boolean_|_nil_] dump BigDecimal as a decimal number or as a String
* - *:bigdecimal_load* [_:bigdecimal_|_:float_|_:auto_] load decimals as BigDecimal instead of as a Float. :auto pick the most precise for the number of digits.
* - *:bigdecimal_load* [_:bigdecimal_|_:float_|_:auto_|_:fast_] load decimals as BigDecimal instead of as a Float. :auto pick the most precise for the number of digits. :float should be the same as ruby. :fast may require rounding but is must faster.
* - *:create_id* [_String_|_nil_] create id for json compatible object encoding, default is 'json_class'
* - *:create_additions* [_Boolean_|_nil_] if true allow creation of instances using create_id on load.
* - *:second_precision* [_Fixnum_|_nil_] number of digits after the decimal when dumping the seconds portion of time
Expand Down Expand Up @@ -331,6 +332,7 @@ get_def_opts(VALUE self) {
switch (oj_default_options.bigdec_load) {
case BigDec: rb_hash_aset(opts, bigdecimal_load_sym, bigdecimal_sym);break;
case FloatDec: rb_hash_aset(opts, bigdecimal_load_sym, float_sym); break;
case FastDec: rb_hash_aset(opts, bigdecimal_load_sym, fast_sym); break;
case AutoDec:
default: rb_hash_aset(opts, bigdecimal_load_sym, auto_sym); break;
}
Expand Down Expand Up @@ -574,6 +576,8 @@ oj_parse_options(VALUE ropts, Options copts) {
copts->bigdec_load = BigDec;
} else if (float_sym == v) {
copts->bigdec_load = FloatDec;
} else if (fast_sym == v) {
copts->bigdec_load = FastDec;
} else if (auto_sym == v || Qfalse == v) {
copts->bigdec_load = AutoDec;
} else {
Expand Down Expand Up @@ -1654,6 +1658,7 @@ Init_oj() {
empty_string_sym = ID2SYM(rb_intern("empty_string")); rb_gc_register_address(&empty_string_sym);
escape_mode_sym = ID2SYM(rb_intern("escape_mode")); rb_gc_register_address(&escape_mode_sym);
integer_range_sym = ID2SYM(rb_intern("integer_range")); rb_gc_register_address(&integer_range_sym);
fast_sym = ID2SYM(rb_intern("fast")); rb_gc_register_address(&fast_sym);
float_prec_sym = ID2SYM(rb_intern("float_precision")); rb_gc_register_address(&float_prec_sym);
float_sym = ID2SYM(rb_intern("float")); rb_gc_register_address(&float_sym);
huge_sym = ID2SYM(rb_intern("huge")); rb_gc_register_address(&huge_sym);
Expand Down
3 changes: 2 additions & 1 deletion ext/oj/oj.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ typedef enum {
typedef enum {
BigDec = 'b',
FloatDec = 'f',
AutoDec = 'a'
AutoDec = 'a',
FastDec = 'F'
} BigLoad;

typedef enum {
Expand Down
81 changes: 76 additions & 5 deletions ext/oj/parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,8 @@ read_num(ParseInfo pi) {
ni.nan = 0;
ni.neg = 0;
ni.has_exp = 0;
ni.no_big = (FloatDec == pi->options.bigdec_load);
ni.no_big = (FloatDec == pi->options.bigdec_load || FastDec == pi->options.bigdec_load);
ni.bigdec_load = pi->options.bigdec_load;

if ('-' == *pi->cur) {
pi->cur++;
Expand Down Expand Up @@ -759,6 +760,59 @@ parse_big_decimal(VALUE str) {
return rb_funcall(rb_cObject, oj_bigdecimal_id, 1, str);
}

static long double exp_plus[] = {
1.0,
1.0e1,
1.0e2,
1.0e3,
1.0e4,
1.0e5,
1.0e6,
1.0e7,
1.0e8,
1.0e9,
1.0e10,
1.0e11,
1.0e12,
1.0e13,
1.0e14,
1.0e15,
1.0e16,
1.0e17,
1.0e18,
1.0e19,
1.0e20,
1.0e21,
1.0e22,
1.0e23,
1.0e24,
1.0e25,
1.0e26,
1.0e27,
1.0e28,
1.0e29,
1.0e30,
1.0e31,
1.0e32,
1.0e33,
1.0e34,
1.0e35,
1.0e36,
1.0e37,
1.0e38,
1.0e39,
1.0e40,
1.0e41,
1.0e42,
1.0e43,
1.0e44,
1.0e45,
1.0e46,
1.0e47,
1.0e48,
1.0e49,
};

VALUE
oj_num_as_value(NumInfo ni) {
volatile VALUE rnum = Qnil;
Expand Down Expand Up @@ -802,6 +856,27 @@ oj_num_as_value(NumInfo ni) {
if (ni->no_big) {
rnum = rb_funcall(rnum, rb_intern("to_f"), 0);
}
} else if (FastDec == ni->bigdec_load) {
long double ld = (long double)ni->i * (long double)ni->div + (long double)ni->num;
int x = (int)((int64_t)ni->exp - ni->di);

if (0 < x) {
if (x < (int)(sizeof(exp_plus) / sizeof(*exp_plus))) {
ld *= exp_plus[x];
} else {
ld *= powl(10.0, x);
}
} else if (x < 0) {
if (-x < (int)(sizeof(exp_plus) / sizeof(*exp_plus))) {
ld /= exp_plus[-x];
} else {
ld /= powl(10.0, -x);
}
}
if (ni->neg) {
ld = -ld;
}
rnum = rb_float_new((double)ld);
} else {
char *end;
double d = strtod(ni->str, &end);
Expand All @@ -810,10 +885,6 @@ oj_num_as_value(NumInfo ni) {
rb_raise(oj_parse_error_class, "Invalid float");
}
rnum = rb_float_new(d);

// TBD Alternative if needed.
//volatile VALUE bd = rb_str_new(ni->str, ni->len);
//rnum = rb_Float(bd);
}
}
return rnum;
Expand Down
1 change: 1 addition & 0 deletions ext/oj/parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ typedef struct _numInfo {
int neg;
int has_exp;
int no_big;
int bigdec_load;
} *NumInfo;

typedef struct _parseInfo {
Expand Down
7 changes: 5 additions & 2 deletions ext/oj/sparse.c
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,8 @@ read_num(ParseInfo pi) {
ni.nan = 0;
ni.neg = 0;
ni.has_exp = 0;
ni.no_big = (FloatDec == pi->options.bigdec_load);
ni.no_big = (FloatDec == pi->options.bigdec_load || FastDec == pi->options.bigdec_load);
ni.bigdec_load = pi->options.bigdec_load;

c = reader_get(&pi->rd);
if ('-' == c) {
Expand Down Expand Up @@ -548,7 +549,8 @@ read_nan(ParseInfo pi) {
ni.infinity = 0;
ni.nan = 1;
ni.neg = 0;
ni.no_big = (FloatDec == pi->options.bigdec_load);
ni.no_big = (FloatDec == pi->options.bigdec_load || FastDec == pi->options.bigdec_load);
ni.bigdec_load = pi->options.bigdec_load;

if ('a' != reader_get(&pi->rd) ||
('N' != (c = reader_get(&pi->rd)) && 'n' != c)) {
Expand Down Expand Up @@ -746,6 +748,7 @@ oj_sparse2(ParseInfo pi) {
ni.nan = 1;
ni.neg = 0;
ni.no_big = (FloatDec == pi->options.bigdec_load);
ni.bigdec_load = pi->options.bigdec_load;
add_num_value(pi, &ni);
} else {
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid token");
Expand Down
6 changes: 6 additions & 0 deletions test/test_custom.rb
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ def test_float_parse
assert_equal(Float, f.class)
end

def test_float_parse_fast
f = Oj.load("12.123456789012345678", mode: :custom, bigdecimal_load: :fast);
assert_equal(Float, f.class)
assert_equal(12.123456789012346, f)
end

def test_nan_dump
assert_equal('null', Oj.dump(0/0.0, :nan => :null))
assert_equal('3.3e14159265358979323846', Oj.dump(0/0.0, :nan => :huge))
Expand Down

0 comments on commit 80bee00

Please sign in to comment.