Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize float to bigdecimal #179

Merged
merged 2 commits into from
Jan 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bigdecimal.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ Gem::Specification.new do |s|
ext/bigdecimal/bigdecimal.h
ext/bigdecimal/bits.h
ext/bigdecimal/feature.h
ext/bigdecimal/missing.c
ext/bigdecimal/missing.h
ext/bigdecimal/missing/dtoa.c
ext/bigdecimal/static_assert.h
lib/bigdecimal.rb
lib/bigdecimal/jacobian.rb
Expand Down
117 changes: 114 additions & 3 deletions ext/bigdecimal/bigdecimal.c
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ static ID id_half;
#define BASE1 (BASE/10)

#ifndef DBLE_FIG
#define DBLE_FIG rmpd_double_figures() /* figure of double */
#define DBLE_FIG RMPD_DOUBLE_FIGURES /* figure of double */
#endif

#define LOG10_2 0.3010299956639812
Expand Down Expand Up @@ -205,6 +205,7 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v)
}

static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE);
static VALUE rb_inum_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception);
static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception);
static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception);
static VALUE rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception);
Expand Down Expand Up @@ -2827,8 +2828,118 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception)
rb_raise(rb_eArgError, "precision too large.");
}

val = rb_funcall(val, id_to_r, 0);
return rb_rational_convert_to_BigDecimal(val, digs, raise_exception);
/* Use the same logic in flo_to_s to convert a float to a decimal string */
char buf[DBLE_FIG + BASE_FIG + 2 + 1];
int decpt, negative_p;
char *e;
char *p = BigDecimal_dtoa(d, 2, digs, &decpt, &negative_p, &e);
int len10 = (int)(e - p);
if (len10 >= (int)sizeof(buf))
len10 = (int)sizeof(buf) - 1;
memcpy(buf, p, len10);
xfree(p);

VALUE inum;
size_t RB_UNUSED_VAR(prec) = 0;
size_t exp = 0;
if (decpt > 0) {
if (decpt < len10) {
/*
* len10 |---------------|
* : |-------| frac_len10 = len10 - decpt
* decpt |-------| |--| ntz10 = BASE_FIG - frac_len10 % BASE_FIG
* : : :
* 00 dd dddd.dddd dd 00
* prec |-----.----.----.-----| prec = exp + roomof(frac_len, BASE_FIG)
* exp |-----.----| exp = roomof(decpt, BASE_FIG)
*/
const size_t frac_len10 = len10 - decpt;
const size_t ntz10 = BASE_FIG - frac_len10 % BASE_FIG;
memset(buf + len10, '0', ntz10);
buf[len10 + ntz10] = '\0';
inum = rb_cstr_to_inum(buf, 10, false);

exp = roomof(decpt, BASE_FIG);
prec = exp + roomof(frac_len10, BASE_FIG);
}
else {
/*
* decpt |-----------------------|
* len10 |----------| :
* : |------------| exp10
* : : :
* 00 dd dddd dd 00 0000 0000.0
* : : : :
* : |--| ntz10 = exp10 % BASE_FIG
* prec |-----.----.-----| :
* : |----.----| exp10 / BASE_FIG
* exp |-----.----.-----.----.----|
*/
const size_t exp10 = decpt - len10;
const size_t ntz10 = exp10 % BASE_FIG;

memset(buf + len10, '0', ntz10);
buf[len10 + ntz10] = '\0';
inum = rb_cstr_to_inum(buf, 10, false);

prec = roomof(len10 + ntz10, BASE_FIG);
exp = prec + exp10 / BASE_FIG;
}
}
else if (decpt == 0) {
/*
* len10 |------------|
* : :
* 0.dddd dddd dd 00
* : : :
* : |--| ntz10 = prec * BASE_FIG - len10
* prec |----.----.-----| roomof(len10, BASE_FIG)
*/
prec = roomof(len10, BASE_FIG);
const size_t ntz10 = prec * BASE_FIG - len10;

memset(buf + len10, '0', ntz10);
buf[len10 + ntz10] = '\0';
inum = rb_cstr_to_inum(buf, 10, false);
}
else {
/*
* len10 |---------------|
* : :
* decpt |-------| |--| ntz10 = prec * BASE_FIG - nlz10 - len10
* : : :
* 0.0000 00 dd dddd dddd dd 00
* : : :
* nlz10 |--| : decpt % BASE_FIG
* prec |-----.----.----.-----| roomof(decpt + len10, BASE_FIG) - exp
* exp |----| decpt / BASE_FIG
*/
decpt = -decpt;

const size_t nlz10 = decpt % BASE_FIG;
exp = decpt / BASE_FIG;
prec = roomof(decpt + len10, BASE_FIG) - exp;
const size_t ntz10 = prec * BASE_FIG - nlz10 - len10;

if (nlz10 > 0) {
memmove(buf + nlz10, buf, len10);
memset(buf, '0', nlz10);
}
memset(buf + nlz10 + len10, '0', ntz10);
buf[nlz10 + len10 + ntz10] = '\0';
inum = rb_cstr_to_inum(buf, 10, false);

exp = -exp;
}

VALUE bd = rb_inum_convert_to_BigDecimal(inum, SIZE_MAX, raise_exception);
Real *vp;
TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp);
assert(vp->Prec == prec);
vp->exponent = exp;

if (negative_p) VpSetSign(vp, -1);
return bd;
}

static VALUE
Expand Down
3 changes: 2 additions & 1 deletion ext/bigdecimal/bigdecimal.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ extern VALUE rb_cBigDecimal;
# define RMPD_BASE ((DECDIG)100U)
#endif

#define RMPD_DOUBLE_FIGURES (1+DBL_DIG)

/*
* NaN & Infinity
Expand Down Expand Up @@ -175,7 +176,7 @@ rmpd_base_value(void) { return RMPD_BASE; }
static inline size_t
rmpd_component_figures(void) { return RMPD_COMPONENT_FIGURES; }
static inline size_t
rmpd_double_figures(void) { return 1+DBL_DIG; }
rmpd_double_figures(void) { return RMPD_DOUBLE_FIGURES; }

#define VpBaseFig() rmpd_component_figures()
#define VpDblFig() rmpd_double_figures()
Expand Down
1 change: 1 addition & 0 deletions ext/bigdecimal/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def have_builtin_func(name, check_expr, opt = "", &b)
have_func("finite", "math.h")
have_func("isfinite", "math.h")

have_header("ruby/atomic.h")
have_header("ruby/internal/has/builtin.h")
have_header("ruby/internal/static_assert.h")

Expand Down
17 changes: 17 additions & 0 deletions ext/bigdecimal/missing.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <ruby/ruby.h>

#ifdef HAVE_RUBY_ATOMIC_H
# include <ruby/atomic.h>
#endif

#ifdef RUBY_ATOMIC_PTR_CAS
# define ATOMIC_PTR_CAS(var, old, new) RUBY_ATOMIC_PTR_CAS(var, old, new)
#endif

#undef strtod
#define strtod BigDecimal_strtod
#undef dtoa
#define dtoa BigDecimal_dtoa
#undef hdtoa
#define hdtoa BigDecimal_hdtoa
#include "missing/dtoa.c"
3 changes: 3 additions & 0 deletions ext/bigdecimal/missing.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ finite(double)
# endif
#endif

/* dtoa */
char *BigDecimal_dtoa(double d_, int mode, int ndigits, int *decpt, int *sign, char **rve);

/* rational */

#ifndef HAVE_RB_RATIONAL_NUM
Expand Down
Loading