summaryrefslogtreecommitdiff
path: root/ext/bigdecimal/bigdecimal.c
diff options
context:
space:
mode:
authorKenta Murata <mrkn@mrkn.jp>2021-01-12 22:58:17 +0900
committerKenta Murata <mrkn@mrkn.jp>2021-01-13 02:11:18 +0900
commit4ba3a4491e15ed50f26c8f7a602fb4ef5452085a (patch)
tree1acc0f040522a8db6bc9a73dc996afa9c7dbfd7f /ext/bigdecimal/bigdecimal.c
parent2175c2c957a711f1c48f42d38db202bf52c8bc6f (diff)
[ruby/bigdecimal] Optimize rb_float_convert_to_BigDecimal by using dtoa
This improve the conversion speed several times faster than before. ``` RUBYLIB= BUNDLER_ORIG_RUBYLIB= /home/mrkn/.rbenv/versions/3.0.0/bin/ruby -v -S benchmark-driver /home/mrkn/src/github.com/ruby/bigdecimal/benchmark/from_float.yml ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux] Calculating ------------------------------------- bigdecimal 3.0.0 master flt_e0 156.400k 783.356k i/s - 100.000k times in 0.639388s 0.127656s flt_ep10 158.640k 777.978k i/s - 100.000k times in 0.630359s 0.128538s flt_ep100 101.676k 504.259k i/s - 100.000k times in 0.983512s 0.198311s flt_em10 103.439k 726.339k i/s - 100.000k times in 0.966751s 0.137677s flt_em100 79.675k 651.446k i/s - 100.000k times in 1.255095s 0.153505s Comparison: flt_e0 master: 783355.6 i/s bigdecimal 3.0.0: 156399.5 i/s - 5.01x slower flt_ep10 master: 777977.6 i/s bigdecimal 3.0.0: 158639.7 i/s - 4.90x slower flt_ep100 master: 504259.4 i/s bigdecimal 3.0.0: 101676.5 i/s - 4.96x slower flt_em10 master: 726338.6 i/s bigdecimal 3.0.0: 103439.2 i/s - 7.02x slower flt_em100 master: 651446.3 i/s bigdecimal 3.0.0: 79675.3 i/s - 8.18x slower ``` https://github.com/ruby/bigdecimal/commit/5bdaedd530 https://github.com/ruby/bigdecimal/commit/9bfff57f90 https://github.com/ruby/bigdecimal/commit/d071a0abbb
Diffstat (limited to 'ext/bigdecimal/bigdecimal.c')
-rw-r--r--ext/bigdecimal/bigdecimal.c117
1 files changed, 114 insertions, 3 deletions
diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c
index 4997487572..2d3f09d562 100644
--- a/ext/bigdecimal/bigdecimal.c
+++ b/ext/bigdecimal/bigdecimal.c
@@ -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
@@ -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);
@@ -2824,8 +2825,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