From a5b4b806de3b130348a92aa3306fbb9318efb762 Mon Sep 17 00:00:00 2001 From: Kenta Murata 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 --- ext/bigdecimal/bigdecimal.c | 53 ++++++++++++++++++++------------- ext/bigdecimal/lib/bigdecimal/util.rb | 2 +- test/bigdecimal/test_bigdecimal.rb | 2 ++ test/bigdecimal/test_bigdecimal_util.rb | 8 +++-- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 2d3f09d562..f48f3edb18 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -947,7 +947,7 @@ BigDecimal_coerce(VALUE self, VALUE other) 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) 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) 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) 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) 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) 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) 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) 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) 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) 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) 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) } /* call-seq: - * BigDecimal(initial, digits=0, exception: true) + * BigDecimal(arg, exception: true) + * BigDecimal(arg, digits, exception: true) * - * Create a new BigDecimal object. + * Returns arg converted to a BigDecimal. Numeric types are converted + * directly. Other types except for String are first converted to String + * by to_str. Strings can be converted when it has appropriate + * forms of decimal numbers. Exceptions can be suppressed by passing + * exception: false. * - * initial:: The initial value, as an Integer, a Float, a Rational, - * a BigDecimal, or a String. + * When arg is a Float and digits is 0, the number + * of digits is determined by the algorithm of dtoa 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 arg. * * 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) 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: 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 00a3e967bd..cb645d2a71 100644 --- a/ext/bigdecimal/lib/bigdecimal/util.rb +++ b/ext/bigdecimal/lib/bigdecimal/util.rb @@ -43,7 +43,7 @@ class Float < Numeric # # 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 e8fd85a526..0063fb9743 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -917,6 +917,7 @@ class TestBigDecimal < Test::Unit::TestCase 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 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 9c6973dca7..ffd4c5f679 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -19,11 +19,11 @@ class TestBigDecimalUtil < Test::Unit::TestCase 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) @@ -34,6 +34,8 @@ class TestBigDecimalUtil < Test::Unit::TestCase assert_raise(TypeError) { 0.3.to_d(false) } assert(1.1.to_d.frozen?) + + assert_equal(BigDecimal("999_999.9999"), 999_999.9999.to_d) end def test_Float_to_d_with_precision -- cgit v1.2.3