From d805834c38bee5d8570e6586caa0316d22027459 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 14 Nov 2022 01:59:46 +0900 Subject: [PATCH] WIP Now `BigDecimal(5) / 9` isn't correctly rounded on half down mode. We need to fix `VpMidRound` and/or `VpLeftRound` for correct rounding. --- ext/bigdecimal/bigdecimal.c | 69 +++++++++++++++++++++--------- test/bigdecimal/test_bigdecimal.rb | 34 +++++++++++++-- 2 files changed, 79 insertions(+), 24 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a5b80464..914724e8 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1855,22 +1855,37 @@ static VALUE BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod); */ static VALUE BigDecimal_div(VALUE self, VALUE r) -/* For c = self/r: with round operation */ { - ENTER(5); - Real *c=NULL, *res=NULL, *div = NULL; - r = BigDecimal_divide(self, r, &c, &res, &div); - if (!NIL_P(r)) return r; /* coerced by other */ - SAVE(c); SAVE(res); SAVE(div); - /* a/b = c + r/b */ - /* c xxxxx - r 00000yyyyy ==> (y/b)*BASE >= HALF_BASE - */ - /* Round */ - if (VpHasVal(div)) { /* frac[0] must be zero for NaN,INF,Zero */ - VpInternalRound(c, 0, c->frac[c->Prec-1], (DECDIG)(VpBaseVal() * (DECDIG_DBL)res->frac[0] / div->frac[0])); + VALUE rr = r; + if (is_kind_of_BigDecimal(rr)) { + /* do nothing */ } - return VpCheckGetValue(c); + else if (RB_INTEGER_TYPE_P(r)) { + rr = rb_inum_convert_to_BigDecimal(r, 0, true); + } + else if (RB_TYPE_P(r, T_FLOAT)) { + rr = rb_float_convert_to_BigDecimal(r, 0, true); + } + else if (RB_TYPE_P(r, T_RATIONAL)) { + Real *a; + TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); + rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); + } + + if (!is_kind_of_BigDecimal(rr)) { + return DoSomeOne(self, r, '/'); + } + + ssize_t a_prec, b_prec; + BigDecimal_count_precision_and_scale(self, &a_prec, NULL); + BigDecimal_count_precision_and_scale(rr, &b_prec, NULL); + + size_t n = a_prec + b_prec; + if (n < 2*BIGDECIMAL_DOUBLE_FIGURES) { + n = 2*BIGDECIMAL_DOUBLE_FIGURES; + } + + return BigDecimal_div2(self, rr, SIZET2NUM(n)); } static VALUE BigDecimal_round(int argc, VALUE *argv, VALUE self); @@ -2148,7 +2163,7 @@ BigDecimal_divmod(VALUE self, VALUE r) * Do the same manner as Float#div when n is nil. * Do the same manner as BigDecimal#quo when n is 0. */ -static inline VALUE +static VALUE BigDecimal_div2(VALUE self, VALUE b, VALUE n) { ENTER(5); @@ -7182,9 +7197,14 @@ VpSqrt(Real *y, Real *x) } /* - * Round relatively from the decimal point. - * f: rounding mode - * nf: digit location to round from the decimal point. + * Round y on the specified mode and the location that counted from + * the decimal point. + * + * @param y a BigDecimal number to be rounded + * @param f the rounding mode + * @param nf the decimal location of rounding + * + * @return 1 if rounding was performed, otherwise 0 */ VP_EXPORT int VpMidRound(Real *y, unsigned short f, ssize_t nf) @@ -7348,11 +7368,18 @@ VpMidRound(Real *y, unsigned short f, ssize_t nf) return 1; } -VP_EXPORT int -VpLeftRound(Real *y, unsigned short f, ssize_t nf) /* - * Round from the left hand side of the digits. + * Round y on the specified mode and the location that counted from + * the most significant digit in y->frac[0] including leading zeros. + * + * @param y a BigDecimal number to be rounded + * @param f the rounding mode + * @param nf the decimal location of rounding + * + * @return 1 if rounding was performed, otherwise 0 */ +VP_EXPORT int +VpLeftRound(Real *y, unsigned short f, ssize_t nf) { DECDIG v; if (!VpHasVal(y)) return 0; /* Unable to round */ diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 02282405..09e7a83e 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -973,13 +973,41 @@ def test_div assert_raise_with_message(FloatDomainError, "Computation results in '-Infinity'") { BigDecimal("-1") / 0 } end + def test_div_rounding_mode + BigDecimal.mode(BigDecimal::ROUND_MODE, :half_up) + assert_equal BigDecimal("0.55555555555555555555555555555556"), BigDecimal(5) / 9 + BigDecimal.mode(BigDecimal::ROUND_MODE, :half_down) + assert_equal BigDecimal("0.55555555555555555555555555555555"), BigDecimal(5) / 9 + end + def test_div_gh220 + # https://github.com/ruby/bigdecimal/issues/220 x = BigDecimal("1.0") y = BigDecimal("3672577333.6608990499165058135986328125") - c = BigDecimal("0.272288343892592687909520102748926752911779209181321744700032723729015151607289998e-9") + c = BigDecimal("0.272288343892592687909520102748926752912e-9") + # c = BigDecimal("0.272288343892592687909520102748926752911779209181321744700032723729015151607289998e-9") assert_equal(c, x / y, "[GH-220]") end + def test_div_gh222 + # https://github.com/ruby/bigdecimal/issues/222 + divisor = BigDecimal("1.03") + one = divisor.coerce(1.0)[0] + + x = one + results = 10.times.map { x /= divisor } + + x = one + expected_results = 10.times.map do |i| + x = x.div(divisor, 100).round(results[i].precision, half: :up) + end + + 10.times do |i| + assert_operator results[i].precision, :<=, 119 + assert_equal expected_results[i], results[i] + end + end + def test_div_precision bug13754 = '[ruby-core:82107] [Bug #13754]' a = BigDecimal('101') @@ -1113,7 +1141,7 @@ def test_div_bigdecimal_with_float_and_precision def test_quo_without_prec x = BigDecimal(5) y = BigDecimal(229) - assert_equal(BigDecimal("0.021834061135371179039301310043668122"), x.quo(y)) + assert_equal(BigDecimal("0.021834061135371179039301310043668"), x.quo(y)) end def test_quo_with_prec @@ -1123,7 +1151,7 @@ def test_quo_with_prec x = BigDecimal(5) y = BigDecimal(229) - assert_equal(BigDecimal("0.021834061135371179039301310043668122"), x.quo(y, 0)) + assert_equal(BigDecimal("0.021834061135371179039301310043668"), x.quo(y, 0)) assert_equal(BigDecimal("0.022"), x.quo(y, 2)) assert_equal(BigDecimal("0.0218"), x.quo(y, 3)) assert_equal(BigDecimal("0.0218341"), x.quo(y, 6))