[ruby/bigdecimal] Let BigDecimal#quo accept precision

Fix GH-214.

https://github.com/ruby/bigdecimal/commit/13e0e93f37
This commit is contained in:
Kenta Murata 2021-12-09 22:24:12 +09:00
parent 0b8638cd74
commit 79712fc083
No known key found for this signature in database
GPG Key ID: CEFE8AFB6081B062
2 changed files with 96 additions and 24 deletions

View File

@ -115,6 +115,8 @@ static ID id_half;
*/
static unsigned short VpGetException(void);
static void VpSetException(unsigned short f);
static void VpCheckException(Real *p, bool always);
static VALUE VpCheckGetValue(Real *p);
static void VpInternalRound(Real *c, size_t ixDigit, DECDIG vPrev, DECDIG v);
static int VpLimitRound(Real *c, size_t ixDigit);
static Real *VpCopy(Real *pv, Real const* const x);
@ -165,27 +167,6 @@ is_kind_of_BigDecimal(VALUE const v)
return rb_typeddata_is_kind_of(v, &BigDecimal_data_type);
}
static void
VpCheckException(Real *p, bool always)
{
if (VpIsNaN(p)) {
VpException(VP_EXCEPTION_NaN, "Computation results in 'NaN' (Not a Number)", always);
}
else if (VpIsPosInf(p)) {
VpException(VP_EXCEPTION_INFINITY, "Computation results in 'Infinity'", always);
}
else if (VpIsNegInf(p)) {
VpException(VP_EXCEPTION_INFINITY, "Computation results in '-Infinity'", always);
}
}
static VALUE
VpCheckGetValue(Real *p)
{
VpCheckException(p, false);
return p->obj;
}
NORETURN(static void cannot_be_coerced_into_BigDecimal(VALUE, VALUE));
static void
@ -1656,12 +1637,15 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div)
}
/* call-seq:
* a / b -> bigdecimal
* quo(value) -> bigdecimal
* a / b -> bigdecimal
*
* Divide by the specified value.
*
* The result precision will be the precision of the larger operand,
* but its minimum is 2*Float::DIG.
*
* See BigDecimal#div.
* See BigDecimal#quo.
*/
static VALUE
BigDecimal_div(VALUE self, VALUE r)
@ -1683,6 +1667,45 @@ BigDecimal_div(VALUE self, VALUE r)
return VpCheckGetValue(c);
}
static VALUE BigDecimal_round(int argc, VALUE *argv, VALUE self);
/* call-seq:
* quo(value) -> bigdecimal
* quo(value, digits) -> bigdecimal
*
* Divide by the specified value.
*
* digits:: If specified and less than the number of significant digits of
* the result, the result is rounded to the given number of digits,
* according to the rounding mode indicated by BigDecimal.mode.
*
* If digits is 0 or omitted, the result is the same as for the
* / operator.
*
* See BigDecimal#/.
* See BigDecimal#div.
*/
static VALUE
BigDecimal_quo(int argc, VALUE *argv, VALUE self)
{
VALUE value, digits, result;
SIGNED_VALUE n = -1;
argc = rb_scan_args(argc, argv, "11", &value, &digits);
if (argc > 1) {
n = GetPrecisionInt(digits);
}
if (n > 0) {
result = BigDecimal_div2(self, value, digits);
}
else {
result = BigDecimal_div(self, value);
}
return result;
}
/*
* %: mod = a%b = a - (a.to_f/b).floor * b
* div = (a.to_f/b).floor
@ -1964,6 +1987,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n)
* Document-method: BigDecimal#div
*
* call-seq:
* div(value) -> integer
* div(value, digits) -> bigdecimal or integer
*
* Divide by the specified value.
@ -1978,6 +2002,9 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n)
* If digits is not specified, the result is an integer,
* by analogy with Float#div; see also BigDecimal#divmod.
*
* See BigDecimal#/.
* See BigDecimal#quo.
*
* Examples:
*
* a = BigDecimal("4")
@ -4272,7 +4299,7 @@ Init_bigdecimal(void)
rb_define_method(rb_cBigDecimal, "-@", BigDecimal_neg, 0);
rb_define_method(rb_cBigDecimal, "*", BigDecimal_mult, 1);
rb_define_method(rb_cBigDecimal, "/", BigDecimal_div, 1);
rb_define_method(rb_cBigDecimal, "quo", BigDecimal_div, 1);
rb_define_method(rb_cBigDecimal, "quo", BigDecimal_quo, -1);
rb_define_method(rb_cBigDecimal, "%", BigDecimal_mod, 1);
rb_define_method(rb_cBigDecimal, "modulo", BigDecimal_mod, 1);
rb_define_method(rb_cBigDecimal, "remainder", BigDecimal_remainder, 1);
@ -4446,6 +4473,27 @@ VpSetException(unsigned short f)
bigdecimal_set_thread_local_exception_mode(f);
}
static void
VpCheckException(Real *p, bool always)
{
if (VpIsNaN(p)) {
VpException(VP_EXCEPTION_NaN, "Computation results in 'NaN' (Not a Number)", always);
}
else if (VpIsPosInf(p)) {
VpException(VP_EXCEPTION_INFINITY, "Computation results in 'Infinity'", always);
}
else if (VpIsNegInf(p)) {
VpException(VP_EXCEPTION_INFINITY, "Computation results in '-Infinity'", always);
}
}
static VALUE
VpCheckGetValue(Real *p)
{
VpCheckException(p, false);
return p->obj;
}
/*
* Precision limit.
*/

View File

@ -1101,6 +1101,30 @@ class TestBigDecimal < Test::Unit::TestCase
x.div(y, 100))
end
def test_quo_without_prec
x = BigDecimal(5)
y = BigDecimal(229)
assert_equal(BigDecimal("0.021834061135371179039301310043668122"), x.quo(y))
end
def test_quo_with_prec
begin
saved_mode = BigDecimal.mode(BigDecimal::ROUND_MODE)
BigDecimal.mode(BigDecimal::ROUND_MODE, :half_up)
x = BigDecimal(5)
y = BigDecimal(229)
assert_equal(BigDecimal("0.021834061135371179039301310043668122"), 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))
assert_equal(BigDecimal("0.02183406114"), x.quo(y, 10))
assert_equal(BigDecimal("0.021834061135371179039301310043668122270742358078603"), x.quo(y, 50))
ensure
BigDecimal.mode(BigDecimal::ROUND_MODE, saved_mode)
end
end
def test_abs_bigdecimal
x = BigDecimal((2**100).to_s)
assert_equal(1267650600228229401496703205376, x.abs)