ruby-changes:64851
From: Kenta <ko1@a...>
Date: Wed, 13 Jan 2021 11:49:35 +0900 (JST)
Subject: [ruby-changes:64851] a5b4b806de (master): [ruby/bigdecimal] Allow digits=0 in BigDecimal(flt) and Float#to_d
https://git.ruby-lang.org/ruby.git/commit/?id=a5b4b806de From a5b4b806de3b130348a92aa3306fbb9318efb762 Mon Sep 17 00:00:00 2001 From: Kenta Murata <mrkn@m...> Date: Wed, 13 Jan 2021 10:28:23 +0900 Subject: [ruby/bigdecimal] Allow digits=0 in BigDecimal(flt) and Float#to_d Using dtoa of mode=0, we can determine the number of digits in decimal that is necessary to represent the given Float number without errors. This change permits digits=0 in BigDecimal(flt) and Float#to_d, and these methods use dtoa of mode=0 when the given digits is 0. Internal implicit conversion from Float also uses digits=0. [Fix GH-70] https://github.com/ruby/bigdecimal/commit/2dbe170e35 diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 2d3f09d..f48f3ed 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -947,7 +947,7 @@ BigDecimal_coerce(VALUE self, VALUE other) https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L947 Real *b; if (RB_TYPE_P(other, T_FLOAT)) { - GUARD_OBJ(b, GetVpValueWithPrec(other, DBLE_FIG, 1)); + GUARD_OBJ(b, GetVpValueWithPrec(other, 0, 1)); obj = rb_assoc_new(VpCheckGetValue(b), self); } else { @@ -1005,7 +1005,7 @@ BigDecimal_add(VALUE self, VALUE r) https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L1005 GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1063,7 +1063,7 @@ BigDecimal_sub(VALUE self, VALUE r) https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L1063 GUARD_OBJ(a, GetVpValue(self,1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1113,7 +1113,7 @@ BigDecimalCmp(VALUE self, VALUE r,char op) https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L1113 break; case T_FLOAT: - GUARD_OBJ(b, GetVpValueWithPrec(r, DBLE_FIG, 0)); + GUARD_OBJ(b, GetVpValueWithPrec(r, 0, 0)); break; case T_RATIONAL: @@ -1326,7 +1326,7 @@ BigDecimal_mult(VALUE self, VALUE r) https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L1326 GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1354,7 +1354,7 @@ BigDecimal_divide(Real **c, Real **res, Real **div, VALUE self, VALUE r) https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L1354 GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1420,7 +1420,7 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L1420 GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1521,7 +1521,7 @@ BigDecimal_divremain(VALUE self, VALUE r, Real **dv, Real **rv) https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L1521 GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -2416,7 +2416,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L2416 if (NIL_P(prec)) { n += DBLE_FIG; } - exp = GetVpValueWithPrec(vexp, DBLE_FIG, 1); + exp = GetVpValueWithPrec(vexp, 0, 1); break; case T_RATIONAL: @@ -2829,7 +2829,8 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L2829 char buf[DBLE_FIG + BASE_FIG + 2 + 1]; int decpt, negative_p; char *e; - char *p = BigDecimal_dtoa(d, 2, digs, &decpt, &negative_p, &e); + const int mode = digs == 0 ? 0 : 2; + char *p = BigDecimal_dtoa(d, mode, digs, &decpt, &negative_p, &e); int len10 = (int)(e - p); if (len10 >= (int)sizeof(buf)) len10 = (int)sizeof(buf) - 1; @@ -3006,6 +3007,7 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L3007 VALUE copy = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); vp = VpCopy(NULL, vp); + /* TODO: rounding */ BigDecimal_wrap_struct(copy, vp); return VpCheckGetValue(vp); } @@ -3046,19 +3048,28 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L3048 } /* call-seq: - * BigDecimal(initial, digits=0, exception: true) + * BigDecimal(arg, exception: true) + * BigDecimal(arg, digits, exception: true) * - * Create a new BigDecimal object. + * Returns <i>arg</i> converted to a BigDecimal. Numeric types are converted + * directly. Other types except for String are first converted to String + * by <code>to_str</code>. Strings can be converted when it has appropriate + * forms of decimal numbers. Exceptions can be suppressed by passing + * <code>exception: false</code>. * - * initial:: The initial value, as an Integer, a Float, a Rational, - * a BigDecimal, or a String. + * When <i>arg</i> is a Float and <i>digits</i> is <code>0</code>, the number + * of digits is determined by the algorithm of <code>dtoa</code> function + * written by David M. Gay. That algorithm is based on "How to Print Floating- + * Point Numbers Accurately" by Guy L. Steele, Jr. and Jon L. White [Proc. ACM + * SIGPLAN '90, pp. 112-126]. * - * If it is a String, spaces are ignored and unrecognized characters - * terminate the value. + * arg:: The value converted to a BigDecimal. * - * digits:: The number of significant digits, as an Integer. If omitted or 0, - * the number of significant digits is determined from the initial - * value. + * If it is a String, spaces are ignored and unrecognized characters + * terminate the value. + * + * digits:: The number of significant digits, as an Integer. If omitted, + * the number of significant digits is determined from <i>arg</i>. * * The actual number of significant digits used in computation is * usually larger than the specified number. @@ -3303,7 +3314,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L3314 infinite = isinf(flo); nan = isnan(flo); if (!infinite && !nan) { - vx = GetVpValueWithPrec(x, DBLE_FIG, 0); + vx = GetVpValueWithPrec(x, 0, 0); } break; @@ -3456,7 +3467,7 @@ get_vp_value: https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L3467 infinite = isinf(flo); nan = isnan(flo); if (!zero && !negative && !infinite && !nan) { - vx = GetVpValueWithPrec(x, DBLE_FIG, 1); + vx = GetVpValueWithPrec(x, 0, 1); } break; diff --git a/ext/bigdecimal/lib/bigdecimal/util.rb b/ext/bigdecimal/lib/bigdecimal/util.rb index 00a3e96..cb645d2 100644 --- a/ext/bigdecimal/lib/bigdecimal/util.rb +++ b/ext/bigdecimal/lib/bigdecimal/util.rb @@ -43,7 +43,7 @@ class Float < Numeric https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/lib/bigdecimal/util.rb#L43 # # See also BigDecimal::new. # - def to_d(precision=Float::DIG+1) + def to_d(precision=0) BigDecimal(self, precision) end end diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index e8fd85a..0063fb9 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -917,6 +917,7 @@ class TestBigDecimal < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/bigdecimal/test_bigdecimal.rb#L917 def test_mult_with_float assert_kind_of(BigDecimal, BigDecimal("3") * 1.5) + assert_equal(BigDecimal("64.4"), BigDecimal(1) * 64.4) end def test_mult_with_rational @@ -955,6 +956,7 @@ class TestBigDecimal < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/bigdecimal/test_bigdecimal.rb#L956 def test_div_with_float assert_kind_of(BigDecimal, BigDecimal("3") / 1.5) + assert_equal(BigDecimal("0.5"), BigDecimal(1) / 2.0) end def test_div_with_rational diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index 9c6973d..ffd4c5f 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -19,11 +19,11 @@ class TestBigDecimalUtil < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/bigdecimal/test_bigdecimal_util.rb#L19 def test_Float_to_d_without_precision delta = 1.0/10**(Float::DIG+1) - assert_in_delta(BigDecimal(0.5, Float::DIG+1), 0.5.to_d, delta) - assert_in_delta(BigDecimal(355.0/113.0, Float::DIG+1), (355.0/113.0).to_d, delta) + assert_in_delta(BigDecimal(0.5, 0), 0.5.to_d, delta) + assert_in_delta(BigDecimal(355.0/113.0, 0), (355.0/113.0).to_d, delta) assert_equal(9.05, 9.05.to_d.to_f) - assert_equal("9.050000000000001", 9.05.to_d.to_s('F')) + assert_equal("9.05", 9.05.to_d.to_s('F')) assert_equal(Math::PI, Math::PI.to_d.to_f) (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/