diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a707295e..61a23f21 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -116,6 +116,9 @@ static struct { #define BIGDECIMAL_POSITIVE_P(bd) ((bd)->sign > 0) #define BIGDECIMAL_NEGATIVE_P(bd) ((bd)->sign < 0) +/* Maximum precision to add in division, to ensure at most linear growth */ +#define BIGDECIMAL_MAX_DIVISION_PRECISION_GROWTH 100 + /* * ================== Memory allocation ============================ */ @@ -1822,10 +1825,10 @@ static VALUE BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) /* For c = self.div(r): with round operation */ { - ENTER(5); + ENTER(7); Real *a, *b; ssize_t a_prec, b_prec; - size_t mx; + size_t mx, double_mx, max_mx; TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); SAVE(a); @@ -1855,7 +1858,13 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) BigDecimal_count_precision_and_scale(self, &a_prec, NULL); BigDecimal_count_precision_and_scale(rr, &b_prec, NULL); mx = (a_prec > b_prec) ? a_prec : b_prec; - mx *= 2; + + /* Try a reasonable precision for division, but prevent exponential growth */ + double_mx = mx * 2; + + max_mx = mx + BIGDECIMAL_MAX_DIVISION_PRECISION_GROWTH; + + mx = (double_mx < max_mx) ? double_mx : max_mx; if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) mx = 2*BIGDECIMAL_DOUBLE_FIGURES; @@ -1946,11 +1955,11 @@ BigDecimal_quo(int argc, VALUE *argv, VALUE self) static VALUE BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) { - ENTER(8); + ENTER(10); Real *c=NULL, *d=NULL, *res=NULL; Real *a, *b; ssize_t a_prec, b_prec; - size_t mx; + size_t mx, double_mx, max_mx; TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); SAVE(a); @@ -2011,7 +2020,13 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) BigDecimal_count_precision_and_scale(rr, &b_prec, NULL); mx = (a_prec > b_prec) ? a_prec : b_prec; - mx *= 2; + + /* Try a reasonable precision for division, but prevent exponential growth */ + double_mx = mx * 2; + + max_mx = mx + BIGDECIMAL_MAX_DIVISION_PRECISION_GROWTH; + + mx = (double_mx < max_mx) ? double_mx : max_mx; if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) mx = 2*BIGDECIMAL_DOUBLE_FIGURES; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index fab9622a..f784db29 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -986,6 +986,20 @@ def test_div_gh220 assert_equal(c, x / y, "[GH-220]") end + def test_div_gh222 + # Prevent exponential precision growth + + high_prec = BigDecimal("1.0") + 10.times { high_prec /= BigDecimal("1.000001") } # Accumulate precision + + result = high_prec / BigDecimal("1.03") + + # Test that we don't get exponential precision growth + assert(result.precision < 2 * high_prec.precision, + "Division precision should not grow exponentially. " \ + "Got #{result.precision}, expected < #{2 * high_prec.precision} [GH-222]") + end + def test_div_precision bug13754 = '[ruby-core:82107] [Bug #13754]' a = BigDecimal('101')