Skip to content

Commit 291ae1c

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 291ae1c

File tree

2 files changed

+120
-3
lines changed

2 files changed

+120
-3
lines changed

ext/bigdecimal/bigdecimal.c

Lines changed: 79 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,32 @@ 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;
5883+
VALUE a2, b2, ab;
5884+
BDVALUE c2;
5885+
ap = VpCopy(NULL, a);
5886+
bp = VpCopy(NULL, b);
5887+
ap->exponent = a->Prec;
5888+
bp->exponent = b->Prec;
5889+
a2 = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0);
5890+
b2 = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0);
5891+
BigDecimal_wrap_struct(a2, ap);
5892+
BigDecimal_wrap_struct(b2, bp);
5893+
ab = rb_funcall(BigDecimal_to_i(a2), '*', 1, BigDecimal_to_i(b2));
5894+
5895+
c2 = GetBDValueMust(ab);
5896+
VpAsgn(c, c2.real, 1);
5897+
if (!AddExponent(c, a->exponent)) return 0;
5898+
if (!AddExponent(c, b->exponent)) return 0;
5899+
if (!AddExponent(c, -a->Prec)) return 0;
5900+
if (!AddExponent(c, -b->Prec)) return 0;
5901+
RB_GC_GUARD(c2.bigdecimal);
5902+
return 1;
5903+
}
5904+
58765905
/*
58775906
* Return number of significant digits
58785907
* c = a * b , Where a = a0a1a2 ... an
@@ -5926,6 +5955,11 @@ VpMult(Real *c, Real *a, Real *b)
59265955
MxIndC = c->MaxPrec - 1;
59275956
MxIndAB = a->Prec + b->Prec - 1;
59285957

5958+
if (a->Prec >= MULT_BY_RB_INTEGER_THRESHOLD && b->Prec >= MULT_BY_RB_INTEGER_THRESHOLD) {
5959+
if (!VpMultWithRubyInteger(c, a, b)) return 0;
5960+
goto Exit;
5961+
}
5962+
59295963
if (MxIndC < MxIndAB) { /* The Max. prec. of c < Prec(a)+Prec(b) */
59305964
w = c;
59315965
c = NewZeroNolimit(1, (size_t)((MxIndAB + 1) * BASE_FIG));
@@ -6000,6 +6034,46 @@ VpMult(Real *c, Real *a, Real *b)
60006034
return c->Prec*BASE_FIG;
60016035
}
60026036

6037+
static void
6038+
VpDivdWithRubyInteger(Real *c, Real *r, Real *a, Real *b)
6039+
{
6040+
Real *ap, *bp;
6041+
BDVALUE c2, r2;
6042+
VALUE a2, b2, divmod, div, mod;
6043+
size_t div_prec = c->MaxPrec - 1;
6044+
size_t base_prec = b->Prec;
6045+
6046+
ap = VpCopy(NULL, a);
6047+
bp = VpCopy(NULL, b);
6048+
VpSetSign(ap, 1);
6049+
VpSetSign(bp, 1);
6050+
ap->exponent = base_prec + div_prec;
6051+
bp->exponent = base_prec;
6052+
a2 = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0);
6053+
b2 = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0);
6054+
BigDecimal_wrap_struct(a2, ap);
6055+
BigDecimal_wrap_struct(b2, bp);
6056+
divmod = rb_funcall(BigDecimal_to_i(a2), rb_intern("divmod"), 1, BigDecimal_to_i(b2));
6057+
if (RB_TYPE_P(divmod, T_ARRAY) && RARRAY_LEN(divmod) == 2) {
6058+
div = RARRAY_AREF(divmod, 0);
6059+
mod = RARRAY_AREF(divmod, 1);
6060+
} else {
6061+
div = mod = Qnil;
6062+
}
6063+
6064+
c2 = GetBDValueMust(div);
6065+
r2 = GetBDValueMust(mod);
6066+
VpAsgn(c, c2.real, VpGetSign(a) * VpGetSign(b));
6067+
VpAsgn(r, r2.real, VpGetSign(a));
6068+
RB_GC_GUARD(c2.bigdecimal);
6069+
RB_GC_GUARD(r2.bigdecimal);
6070+
AddExponent(c, a->exponent);
6071+
AddExponent(c, -b->exponent);
6072+
AddExponent(c, -div_prec);
6073+
AddExponent(r, a->exponent);
6074+
AddExponent(r, -base_prec - div_prec);
6075+
}
6076+
60036077
/*
60046078
* c = a / b, remainder = r
60056079
* XXXX_YYYY_ZZZZ / 0001 = XXXX_YYYY_ZZZZ
@@ -6041,6 +6115,11 @@ VpDivd(Real *c, Real *r, Real *a, Real *b)
60416115

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

6118+
if (word_c >= DIV_BY_RB_INTEGER_THRESHOLD && word_b >= DIV_BY_RB_INTEGER_THRESHOLD) {
6119+
VpDivdWithRubyInteger(c, r, a, b);
6120+
goto Exit;
6121+
}
6122+
60446123
for (i = 0; i < word_a; ++i) r->frac[i] = a->frac[i];
60456124
for (i = word_a; i < word_r; ++i) r->frac[i] = 0;
60466125
for (i = 0; i < word_c; ++i) c->frac[i] = 0;

test/bigdecimal/test_vp_operation.rb

Lines changed: 41 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,15 @@ 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+
assert_equal(BigDecimal("-#{x * y}e-46"), BigDecimal("#{x}e-12").vpmult(BigDecimal("-#{y}e-34")))
28+
assert_equal(BigDecimal("-#{x * y}e-333"), BigDecimal("-#{x}e12").vpmult(BigDecimal("#{y}e-345")))
29+
assert_equal(BigDecimal("#{x * y}e46"), BigDecimal("-#{x}e12").vpmult(BigDecimal("-#{y}e34")))
30+
end
2231
end
2332

2433
def test_vpdivd
@@ -84,6 +93,35 @@ def test_vpdivd_precisions
8493
end
8594
end
8695

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

0 commit comments

Comments
 (0)