Skip to content

Commit 28903c4

Browse files
authored
Use minimum necessary division precision in BigDecimal_DoDivmod (#371)
Integer division part of a.divmod(b) only needs (a.exponent-b.exponent+1) digits precision.
1 parent 6af475d commit 28903c4

File tree

2 files changed

+72
-53
lines changed

2 files changed

+72
-53
lines changed

ext/bigdecimal/bigdecimal.c

Lines changed: 42 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,14 @@ check_allocation_count_nonzero(void)
174174
# define check_allocation_count_nonzero() /* nothing */
175175
#endif /* BIGDECIMAL_DEBUG */
176176

177+
/* VpMult VpDivd helpers */
178+
#define VPMULT_RESULT_PREC(a, b) (a->Prec + b->Prec + 1)
179+
/* To calculate VpDivd with n-digits precision, quotient needs n+2*BASE_FIG-1 digits space */
180+
/* In the worst precision case 0001_1111_1111 / 9999 = 0000_0001_1112, there are 2*BASE_FIG-1 leading zeros */
181+
#define VPDIVD_QUO_DIGITS(required_digits) ((required_digits) + 2 * BASE_FIG - 1)
182+
/* Required r.MaxPrec for calculating VpDivd(c, r, a, b) */
183+
#define VPDIVD_REM_PREC(a, b, c) Max(a->Prec, b->Prec + c->MaxPrec - 1)
184+
177185
static NULLABLE_BDVALUE
178186
CreateFromString(size_t mx, const char *str, VALUE klass, bool strict_p, bool raise_exception);
179187

@@ -222,16 +230,6 @@ rbd_allocate_struct_decimal_digits(size_t const decimal_digits, bool limit_preci
222230

223231
static VALUE BigDecimal_wrap_struct(VALUE obj, Real *vp);
224232

225-
static BDVALUE
226-
rbd_reallocate_struct(BDVALUE value, size_t const internal_digits)
227-
{
228-
size_t const size = rbd_struct_size(internal_digits);
229-
Real *new_real = (Real *)ruby_xrealloc(value.real, size);
230-
new_real->MaxPrec = internal_digits;
231-
BigDecimal_wrap_struct(value.bigdecimal, new_real);
232-
return (BDVALUE) { value.bigdecimal, new_real };
233-
}
234-
235233
static void
236234
rbd_free_struct(Real *real)
237235
{
@@ -1843,7 +1841,6 @@ BigDecimal_mult(VALUE self, VALUE r)
18431841
{
18441842
ENTER(5);
18451843
BDVALUE a, b, c;
1846-
size_t mx;
18471844

18481845
GUARD_OBJ(a, GetBDValueMust(self));
18491846
if (RB_TYPE_P(r, T_FLOAT)) {
@@ -1859,8 +1856,7 @@ BigDecimal_mult(VALUE self, VALUE r)
18591856
b = bdvalue_nonnullable(b2);
18601857
}
18611858

1862-
mx = a.real->Prec + b.real->Prec;
1863-
GUARD_OBJ(c, NewZeroWrapLimited(1, (mx + 1) * BASE_FIG));
1859+
GUARD_OBJ(c, NewZeroWrapLimited(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG));
18641860
VpMult(c.real, a.real, b.real);
18651861
return CheckGetValue(c);
18661862
}
@@ -1941,9 +1937,9 @@ static VALUE
19411937
BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE *mod, bool truncate)
19421938
{
19431939
ENTER(8);
1944-
BDVALUE a, b, c, d, e, res;
1945-
ssize_t a_prec, b_prec;
1946-
size_t mx;
1940+
BDVALUE a, b, dv, md, res;
1941+
ssize_t a_exponent, b_exponent;
1942+
size_t mx, rx;
19471943

19481944
GUARD_OBJ(a, GetBDValueMust(self));
19491945

@@ -1997,39 +1993,36 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, NULLABLE_BDVALUE *div, NULLABLE_BDVALUE
19971993
return Qtrue;
19981994
}
19991995

2000-
BigDecimal_count_precision_and_scale(self, &a_prec, NULL);
2001-
BigDecimal_count_precision_and_scale(rr, &b_prec, NULL);
1996+
a_exponent = VpExponent10(a.real);
1997+
b_exponent = VpExponent10(b.real);
1998+
mx = a_exponent > b_exponent ? a_exponent - b_exponent + 1 : 1;
1999+
GUARD_OBJ(dv, NewZeroWrapLimited(1, VPDIVD_QUO_DIGITS(mx)));
20022000

2003-
mx = (a_prec > b_prec) ? a_prec : b_prec;
2004-
mx *= 2;
2001+
/* res is reused for VpDivd remainder and VpMult result */
2002+
rx = VPDIVD_REM_PREC(a.real, b.real, dv.real);
2003+
mx = VPMULT_RESULT_PREC(dv.real, b.real);
2004+
GUARD_OBJ(res, NewZeroWrapNolimit(1, Max(rx, mx) * BASE_FIG));
2005+
/* AddSub needs one more prec */
2006+
GUARD_OBJ(md, NewZeroWrapLimited(1, (res.real->MaxPrec + 1) * BASE_FIG));
20052007

2006-
if (2*BIGDECIMAL_DOUBLE_FIGURES > mx)
2007-
mx = 2*BIGDECIMAL_DOUBLE_FIGURES;
2008+
VpDivd(dv.real, res.real, a.real, b.real);
2009+
VpMidRound(dv.real, VP_ROUND_DOWN, 0);
2010+
VpMult(res.real, dv.real, b.real);
2011+
VpAddSub(md.real, a.real, res.real, -1);
20082012

2009-
GUARD_OBJ(c, NewZeroWrapLimited(1, mx + 2*BASE_FIG));
2010-
GUARD_OBJ(res, NewZeroWrapNolimit(1, mx*2 + 2*BASE_FIG));
2011-
VpDivd(c.real, res.real, a.real, b.real);
2012-
2013-
mx = c.real->Prec * BASE_FIG;
2014-
GUARD_OBJ(d, NewZeroWrapLimited(1, mx));
2015-
VpActiveRound(d.real, c.real, VP_ROUND_DOWN, 0);
2016-
2017-
VpMult(res.real, d.real, b.real);
2018-
VpAddSub(c.real, a.real, res.real, -1);
2019-
2020-
if (!truncate && !VpIsZero(c.real) && (VpGetSign(a.real) * VpGetSign(b.real) < 0)) {
2013+
if (!truncate && !VpIsZero(md.real) && (VpGetSign(a.real) * VpGetSign(b.real) < 0)) {
20212014
/* result adjustment for negative case */
2022-
res = rbd_reallocate_struct(res, d.real->MaxPrec);
2023-
res.real->MaxPrec = d.real->MaxPrec;
2024-
VpAddSub(res.real, d.real, VpOne(), -1);
2025-
GUARD_OBJ(e, NewZeroWrapLimited(1, GetAddSubPrec(c.real, b.real) * 2*BASE_FIG));
2026-
VpAddSub(e.real, c.real, b.real, 1);
2027-
*div = bdvalue_nullable(res);
2028-
*mod = bdvalue_nullable(e);
2015+
BDVALUE dv2, md2;
2016+
GUARD_OBJ(dv2, NewZeroWrapLimited(1, (dv.real->MaxPrec + 1) * BASE_FIG));
2017+
GUARD_OBJ(md2, NewZeroWrapLimited(1, (GetAddSubPrec(md.real, b.real) + 1) * BASE_FIG));
2018+
VpAddSub(dv2.real, dv.real, VpOne(), -1);
2019+
VpAddSub(md2.real, md.real, b.real, 1);
2020+
*div = bdvalue_nullable(dv2);
2021+
*mod = bdvalue_nullable(md2);
20292022
}
20302023
else {
2031-
*div = bdvalue_nullable(d);
2032-
*mod = bdvalue_nullable(c);
2024+
*div = bdvalue_nullable(dv);
2025+
*mod = bdvalue_nullable(md);
20332026
}
20342027
return Qtrue;
20352028

@@ -2121,7 +2114,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n)
21212114
ENTER(5);
21222115
SIGNED_VALUE ix;
21232116
BDVALUE av, bv, cv, res;
2124-
size_t mx, pl;
2117+
size_t pl;
21252118

21262119
if (NIL_P(n)) { /* div in Float sense */
21272120
NULLABLE_BDVALUE div;
@@ -2158,11 +2151,9 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n)
21582151
ix = 2 * BIGDECIMAL_DOUBLE_FIGURES;
21592152
}
21602153

2161-
// VpDivd needs 2 extra DECDIGs for rounding.
2162-
GUARD_OBJ(cv, NewZeroWrapLimited(1, ix + 2 * VpBaseFig()));
2163-
2164-
mx = Max(av.real->Prec, bv.real->Prec + cv.real->MaxPrec - 1);
2165-
GUARD_OBJ(res, NewZeroWrapNolimit(1, mx * VpBaseFig()));
2154+
// Needs to calculate 1 extra digit for rounding.
2155+
GUARD_OBJ(cv, NewZeroWrapLimited(1, VPDIVD_QUO_DIGITS(ix + 1)));
2156+
GUARD_OBJ(res, NewZeroWrapNolimit(1, VPDIVD_REM_PREC(av.real, bv.real, cv.real) * BASE_FIG));
21662157
VpDivd(cv.real, res.real, av.real, bv.real);
21672158
VpSetPrecLimit(pl);
21682159
if (!VpIsZero(res.real)) {
@@ -4221,9 +4212,8 @@ BigDecimal_vpdivd(VALUE self, VALUE r, VALUE cprec) {
42214212
size_t cn = NUM2INT(cprec);
42224213
a = GetBDValueMust(self);
42234214
b = GetBDValueMust(r);
4224-
size_t dn = Max(a.real->Prec, b.real->Prec + cn - 1);
42254215
c = NewZeroWrapLimited(1, cn * BASE_FIG);
4226-
d = NewZeroWrapLimited(1, dn * BASE_FIG);
4216+
d = NewZeroWrapLimited(1, VPDIVD_REM_PREC(a.real, b.real, c.real) * BASE_FIG);
42274217
VpDivd(c.real, d.real, a.real, b.real);
42284218
VpNmlz(c.real);
42294219
VpNmlz(d.real);
@@ -4237,8 +4227,7 @@ BigDecimal_vpmult(VALUE self, VALUE v) {
42374227
BDVALUE a,b,c;
42384228
a = GetBDValueMust(self);
42394229
b = GetBDValueMust(v);
4240-
size_t cn = a.real->Prec + b.real->Prec + 1;
4241-
c = NewZeroWrapLimited(1, cn * BASE_FIG);
4230+
c = NewZeroWrapLimited(1, VPMULT_RESULT_PREC(a.real, b.real) * BASE_FIG);
42424231
VpMult(c.real, a.real, b.real);
42434232
VpNmlz(c.real);
42444233
RB_GC_GUARD(a.bigdecimal);

test/bigdecimal/test_bigdecimal.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,14 @@ def test_div_various_precisions
11071107
end
11081108
end
11091109

1110+
def test_div_round_worst_precision_case
1111+
x = BigDecimal(5)
1112+
y = BigDecimal(9 * BASE / 10)
1113+
(BASE_FIG * 2..BASE_FIG * 4).each do |prec|
1114+
assert_equal(BigDecimal('0.' + '5' * (prec - 1) + "6E-#{BASE_FIG - 1}"), x.div(y, prec))
1115+
end
1116+
end
1117+
11101118
def test_div_rounding_with_small_remainder
11111119
BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_UP)
11121120
assert_equal(BigDecimal('0.12e1'), BigDecimal('1.25').div(BigDecimal("1.#{'0' * 30}1"), 2))
@@ -1210,6 +1218,28 @@ def test_divmod_precision
12101218
q, r = a.divmod(b)
12111219
assert_equal((a/b).round(0, :down) - 1, q)
12121220
assert_equal((a - q*b), r)
1221+
1222+
a = BigDecimal('3e100')
1223+
b = BigDecimal('-1.7e-100')
1224+
q, r = a.divmod(b)
1225+
assert_include(0...-b, -r)
1226+
assert_equal((a - q*b), r)
1227+
1228+
a = BigDecimal('0.32e23')
1229+
b = BigDecimal('-0.1999999999e-23')
1230+
q, r = a.divmod(b)
1231+
assert_include(0...-b, -r)
1232+
assert_equal((a - q*b), r)
1233+
1234+
a = BigDecimal('199.9999999999999999999999999')
1235+
q, r = a.divmod(1)
1236+
assert_equal([199, a - 199], [q, r])
1237+
1238+
a = BigDecimal('0.30000000000000000000000000000000000000000000000000000000000000001e91')
1239+
b = BigDecimal('0.1e20')
1240+
q, r = a.divmod(b)
1241+
assert_include(0...b, r)
1242+
assert_equal((a - q*b), r)
12131243
end
12141244

12151245
def test_divmod_error

0 commit comments

Comments
 (0)