Skip to content

Commit 67fcd5d

Browse files
committed
Calculate division and multiplication with Ruby's Integer for large precision
Ruby's Integer implements faster algorithm (karatsuba, toom3, GMP)
1 parent 073edce commit 67fcd5d

File tree

2 files changed

+112
-3
lines changed

2 files changed

+112
-3
lines changed

ext/bigdecimal/bigdecimal.c

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ bdvalue_nullable(BDVALUE v)
145145
#define BIGDECIMAL_POSITIVE_P(bd) ((bd)->sign > 0)
146146
#define BIGDECIMAL_NEGATIVE_P(bd) ((bd)->sign < 0)
147147

148+
#define MULT_BY_RB_INTEGER_THRESHOLD 150
149+
#define DIV_BY_RB_INTEGER_THRESHOLD 300
150+
148151
/*
149152
* ================== Memory allocation ============================
150153
*/
@@ -5873,6 +5876,31 @@ VpSetPTR(Real *a, Real *b, Real *c, size_t *a_pos, size_t *b_pos, size_t *c_pos,
58735876
return word_shift;
58745877
}
58755878

5879+
static int
5880+
VpMultWithRubyInteger(Real *c, Real *a, Real *b)
5881+
{
5882+
Real *ap, *bp, *cp;
5883+
VALUE a2, b2, ab, c2;
5884+
ap = VpCopy(NULL, a);
5885+
bp = VpCopy(NULL, b);
5886+
ap->exponent = a->Prec;
5887+
bp->exponent = b->Prec;
5888+
a2 = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0);
5889+
b2 = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0);
5890+
BigDecimal_wrap_struct(a2, ap);
5891+
BigDecimal_wrap_struct(b2, bp);
5892+
ab = rb_big_mul(BigDecimal_to_i(a2), BigDecimal_to_i(b2));
5893+
c2 = rb_inum_convert_to_BigDecimal(ab, 0, true);
5894+
cp = DATA_PTR(c2);
5895+
VpAsgn(c, cp, 1);
5896+
if (!AddExponent(c, a->exponent)) return 0;
5897+
if (!AddExponent(c, b->exponent)) return 0;
5898+
if (!AddExponent(c, -a->Prec)) return 0;
5899+
if (!AddExponent(c, -b->Prec)) return 0;
5900+
RB_GC_GUARD(c2);
5901+
return 1;
5902+
}
5903+
58765904
/*
58775905
* Return number of significant digits
58785906
* c = a * b , Where a = a0a1a2 ... an
@@ -5926,6 +5954,11 @@ VpMult(Real *c, Real *a, Real *b)
59265954
MxIndC = c->MaxPrec - 1;
59275955
MxIndAB = a->Prec + b->Prec - 1;
59285956

5957+
if (a->Prec >= MULT_BY_RB_INTEGER_THRESHOLD && b->Prec >= MULT_BY_RB_INTEGER_THRESHOLD) {
5958+
if (!VpMultWithRubyInteger(c, a, b)) return 0;
5959+
goto Exit;
5960+
}
5961+
59295962
if (MxIndC < MxIndAB) { /* The Max. prec. of c < Prec(a)+Prec(b) */
59305963
w = c;
59315964
c = NewZeroNolimit(1, (size_t)((MxIndAB + 1) * BASE_FIG));
@@ -6000,6 +6033,42 @@ VpMult(Real *c, Real *a, Real *b)
60006033
return c->Prec*BASE_FIG;
60016034
}
60026035

6036+
static void
6037+
VpDivdWithRubyInteger(Real *c, Real *r, Real *a, Real *b)
6038+
{
6039+
Real *ap, *bp;
6040+
BDVALUE c2, r2;
6041+
VALUE a2, b2, divmod, div, mod;
6042+
size_t div_prec = c->MaxPrec - 1;
6043+
size_t base_prec = b->Prec;
6044+
6045+
ap = VpCopy(NULL, a);
6046+
bp = VpCopy(NULL, b);
6047+
VpSetSign(ap, 1);
6048+
VpSetSign(bp, 1);
6049+
ap->exponent = base_prec + div_prec;
6050+
bp->exponent = base_prec;
6051+
a2 = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0);
6052+
b2 = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0);
6053+
BigDecimal_wrap_struct(a2, ap);
6054+
BigDecimal_wrap_struct(b2, bp);
6055+
divmod = rb_big_divmod(BigDecimal_to_i(a2), BigDecimal_to_i(b2));
6056+
div = RARRAY_AREF(divmod, 0);
6057+
mod = RARRAY_AREF(divmod, 1);
6058+
6059+
c2 = GetBDValueMust(div);
6060+
r2 = GetBDValueMust(mod);
6061+
VpAsgn(c, c2.real, VpGetSign(a) * VpGetSign(b));
6062+
VpAsgn(r, r2.real, VpGetSign(a));
6063+
RB_GC_GUARD(c2.bigdecimal);
6064+
RB_GC_GUARD(r2.bigdecimal);
6065+
AddExponent(c, a->exponent);
6066+
AddExponent(c, -b->exponent);
6067+
AddExponent(c, -div_prec);
6068+
AddExponent(r, a->exponent);
6069+
AddExponent(r, -base_prec - div_prec);
6070+
}
6071+
60036072
/*
60046073
* c = a / b, remainder = r
60056074
* XXXX_YYYY_ZZZZ / 0001 = XXXX_YYYY_ZZZZ
@@ -6041,6 +6110,11 @@ VpDivd(Real *c, Real *r, Real *a, Real *b)
60416110

60426111
if (word_a > word_r || word_b + word_c - 2 >= word_r) goto space_error;
60436112

6113+
if (word_c >= DIV_BY_RB_INTEGER_THRESHOLD && word_b >= DIV_BY_RB_INTEGER_THRESHOLD) {
6114+
VpDivdWithRubyInteger(c, r, a, b);
6115+
goto Exit;
6116+
}
6117+
60446118
for (i = 0; i < word_a; ++i) r->frac[i] = a->frac[i];
60456119
for (i = word_a; i < word_r; ++i) r->frac[i] = 0;
60466120
for (i = 0; i < word_c; ++i) c->frac[i] = 0;

test/bigdecimal/test_vp_operation.rb

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
class TestVpOperation < Test::Unit::TestCase
66
include TestBigDecimalBase
77

8+
INTEGER_MULT_THRESHOLD = 150
9+
INTEGER_DIV_THRESHOLD = 300
10+
811
def setup
912
super
1013
unless BigDecimal.instance_methods.include?(:vpdivd)
@@ -16,9 +19,12 @@ def setup
1619
def test_vpmult
1720
assert_equal(BigDecimal('121932631112635269'), BigDecimal('123456789').vpmult(BigDecimal('987654321')))
1821
assert_equal(BigDecimal('12193263.1112635269'), BigDecimal('123.456789').vpmult(BigDecimal('98765.4321')))
19-
x = 123**456
20-
y = 987**123
21-
assert_equal(BigDecimal("#{x * y}e-300"), BigDecimal("#{x}e-100").vpmult(BigDecimal("#{y}e-200")))
22+
23+
[10, 50, INTEGER_MULT_THRESHOLD, INTEGER_MULT_THRESHOLD + 50].each do |prec|
24+
x = (BASE + BASE / 7) ** prec
25+
y = (BASE + BASE / 3) ** prec
26+
assert_equal(BigDecimal("#{x * y}e-168"), BigDecimal("#{x}e-123").vpmult(BigDecimal("#{y}e-45")))
27+
end
2228
end
2329

2430
def test_vpdivd
@@ -84,6 +90,35 @@ def test_vpdivd_precisions
8490
end
8591
end
8692

93+
def test_vpdivd_by_ruby_integer
94+
# Exponent check
95+
integer_div_prec = INTEGER_DIV_THRESHOLD + 50
96+
x = (BASE + BASE / 7) ** integer_div_prec
97+
y = (BASE + BASE / 3) ** integer_div_prec
98+
bx = BigDecimal("#{x}e-123")
99+
by = BigDecimal("#{y}e-456")
100+
div, mod = bx.vpdivd(by, integer_div_prec)
101+
assert_include(0...by, mod)
102+
assert_equal(bx, div * by + mod)
103+
104+
# Precision should consistent around DIV_BY_RB_INTEGER_THRESHOLD threshold
105+
[2, 3, 4, integer_div_prec, integer_div_prec + 1, integer_div_prec + 2].each do |prec|
106+
a = BigDecimal('1' + '2' * BASE_FIG * prec)
107+
b = BigDecimal('9' * BASE_FIG + '9' * BASE_FIG * prec)
108+
assert_equal(BASE_FIG * (prec - 2) + 1, a.vpdivd(b, prec).first.n_significant_digits)
109+
assert_equal(BASE_FIG * prec, b.vpdivd(a, prec).first.n_significant_digits)
110+
111+
x = BigDecimal("1#{'0' * BASE_FIG * prec}1")
112+
y = BigDecimal("1#{'0' * BASE_FIG * prec}2")
113+
div = BigDecimal("0.#{'9' * BASE_FIG * (prec - 1)}")
114+
mod = BigDecimal("#{'9' * (BASE_FIG + 1)}.#{'0' * (BASE_FIG * (prec - 1) - 1)}2")
115+
assert_equal([div, mod], x.vpdivd(y, prec))
116+
assert_equal([-div, mod], x.vpdivd(-y, prec))
117+
assert_equal([-div, -mod], (-x).vpdivd(y, prec))
118+
assert_equal([div, -mod], (-x).vpdivd(-y, prec))
119+
end
120+
end
121+
87122
def test_vpdivd_borrow
88123
y_small = BASE / 7 * BASE ** 4
89124
y_large = (4 * BASE_FIG).times.map {|i| i % 9 + 1 }.join.to_i

0 commit comments

Comments
 (0)