From 6e43bbbae32156e591964025568d4a57123a94cf Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Wed, 31 Jul 2019 17:52:37 -0700 Subject: [PATCH] Implement Numeric#exact? This returns true for Integer and Rational, and false for Float and BigDecimal. For Complex, it returns true if the real and imaginary parts are both true, and false otherwise. Implements [Feature #5321] --- complex.c | 23 ++++++++++++++++++++++- ext/bigdecimal/bigdecimal.c | 8 ++++++++ numeric.c | 27 +++++++++++++++++++++++++++ test/bigdecimal/test_bigdecimal.rb | 4 ++++ test/ruby/test_complex.rb | 7 +++++++ test/ruby/test_float.rb | 4 ++++ test/ruby/test_integer.rb | 4 ++++ test/ruby/test_rational.rb | 4 ++++ 8 files changed, 80 insertions(+), 1 deletion(-) diff --git a/complex.c b/complex.c index fa00036ffb..24bf1d8f2c 100644 --- a/complex.c +++ b/complex.c @@ -34,7 +34,7 @@ VALUE rb_cComplex; static ID id_abs, id_arg, id_denominator, id_fdiv, id_numerator, id_quo, - id_real_p, id_i_real, id_i_imag, + id_real_p, id_i_real, id_i_imag, id_exact_p, id_finite_p, id_infinite_p, id_rationalize, id_PI; #define id_to_i idTo_i @@ -1393,6 +1393,25 @@ nucomp_inspect(VALUE self) #define FINITE_TYPE_P(v) (RB_INTEGER_TYPE_P(v) || RB_TYPE_P(v, T_RATIONAL)) +/* + * call-seq: + * cmp.exact? -> true or false + * + * Returns +true+ if +cmp+'s real and imaginary parts are both exact numbers, + * otherwise returns +false+. + */ +static VALUE +rb_complex_exact_p(VALUE self) +{ + get_dat1(self); + + if (RTEST(rb_funcall(dat->real, id_exact_p, 0)) && + RTEST(rb_funcall(dat->imag, id_exact_p, 0))) { + return Qtrue; + } + return Qfalse; +} + /* * call-seq: * cmp.finite? -> true or false @@ -2284,6 +2303,7 @@ Init_Complex(void) id_real_p = rb_intern("real?"); id_i_real = rb_intern("@real"); id_i_imag = rb_intern("@image"); /* @image, not @imag */ + id_exact_p = rb_intern("exact?"); id_finite_p = rb_intern("finite?"); id_infinite_p = rb_intern("infinite?"); id_rationalize = rb_intern("rationalize"); @@ -2358,6 +2378,7 @@ Init_Complex(void) rb_undef_method(rb_cComplex, "positive?"); rb_undef_method(rb_cComplex, "negative?"); + rb_define_method(rb_cComplex, "exact?", rb_complex_exact_p, 0); rb_define_method(rb_cComplex, "finite?", rb_complex_finite_p, 0); rb_define_method(rb_cComplex, "infinite?", rb_complex_infinite_p, 0); diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 652f341fb9..9830644b98 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -687,6 +687,13 @@ BigDecimal_IsNaN(VALUE self) return Qfalse; } +/* Returns false as BigDecimal is not exact. */ +static VALUE +BigDecimal_IsExact(VALUE self) +{ + return Qfalse; +} + /* Returns nil, -1, or +1 depending on whether the value is finite, * -Infinity, or +Infinity. */ @@ -3493,6 +3500,7 @@ Init_bigdecimal(void) rb_define_method(rb_cBigDecimal, "exponent", BigDecimal_exponent, 0); rb_define_method(rb_cBigDecimal, "sign", BigDecimal_sign, 0); rb_define_method(rb_cBigDecimal, "nan?", BigDecimal_IsNaN, 0); + rb_define_method(rb_cBigDecimal, "exact?", BigDecimal_IsExact, 0); rb_define_method(rb_cBigDecimal, "infinite?", BigDecimal_IsInfinite, 0); rb_define_method(rb_cBigDecimal, "finite?", BigDecimal_IsFinite, 0); rb_define_method(rb_cBigDecimal, "truncate", BigDecimal_truncate, -1); diff --git a/numeric.c b/numeric.c index 5b0ab8cbe8..78711e4eff 100644 --- a/numeric.c +++ b/numeric.c @@ -795,6 +795,18 @@ num_nonzero_p(VALUE num) return num; } +/* + * call-seq: + * num.exact? -> true or false + * + * Returns +true+ if +num+ is an exact number, otherwise returns +false+. + */ +static VALUE +num_exact_p(VALUE num) +{ + return Qtrue; +} + /* * call-seq: * num.finite? -> true or false @@ -1731,6 +1743,19 @@ flo_is_nan_p(VALUE num) return isnan(value) ? Qtrue : Qfalse; } +/* + * call-seq: + * float.exact? -> false + * + * Returns +false+, as floating point numbers are not exact. + */ + +static VALUE +flo_is_exact_p(VALUE num) +{ + return Qfalse; +} + /* * call-seq: * float.infinite? -> -1, 1, or nil @@ -5586,6 +5611,7 @@ Init_Numeric(void) rb_define_method(rb_cNumeric, "integer?", num_int_p, 0); rb_define_method(rb_cNumeric, "zero?", num_zero_p, 0); rb_define_method(rb_cNumeric, "nonzero?", num_nonzero_p, 0); + rb_define_method(rb_cNumeric, "exact?", num_exact_p, 0); rb_define_method(rb_cNumeric, "finite?", num_finite_p, 0); rb_define_method(rb_cNumeric, "infinite?", num_infinite_p, 0); @@ -5808,6 +5834,7 @@ Init_Numeric(void) rb_define_method(rb_cFloat, "truncate", flo_truncate, -1); rb_define_method(rb_cFloat, "nan?", flo_is_nan_p, 0); + rb_define_method(rb_cFloat, "exact?", flo_is_exact_p, 0); rb_define_method(rb_cFloat, "infinite?", rb_flo_is_infinite_p, 0); rb_define_method(rb_cFloat, "finite?", rb_flo_is_finite_p, 0); rb_define_method(rb_cFloat, "next_float", flo_next_float, 0); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index e4f14449a1..ef3d8595dc 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -477,6 +477,10 @@ def test_nonzero_p assert_nan(BigDecimal("NaN").nonzero?) end + def test_exact_p + assert_not_predicate(BigDecimal('0.0'), :exact?) + end + def test_double_fig assert_kind_of(Integer, BigDecimal.double_fig) end diff --git a/test/ruby/test_complex.rb b/test/ruby/test_complex.rb index 2a72f3bcb9..86853287af 100644 --- a/test/ruby/test_complex.rb +++ b/test/ruby/test_complex.rb @@ -36,6 +36,13 @@ def test_compsub assert_equal([true, true], [c.eql?(c1), c1.eql?(c)]) end + def test_exact_p + assert_predicate(Complex(1), :exact?) + assert_not_predicate(Complex(1, 1.0), :exact?) + assert_not_predicate(Complex(1.0, 1), :exact?) + assert_not_predicate(Complex(1.0, 1.0), :exact?) + end + def test_eql_p c = Complex(0) c2 = Complex(0) diff --git a/test/ruby/test_float.rb b/test/ruby/test_float.rb index 7cbf3b5a8f..055c41ea42 100644 --- a/test/ruby/test_float.rb +++ b/test/ruby/test_float.rb @@ -59,6 +59,10 @@ def test_symmetry_bignum # [ruby-bugs-ja:118] assert_equal(a == b, b == a) end + def test_exact_p + assert_not_predicate(1.0, :exact?) + end + def test_cmp_int 100.times {|i| int0 = 1 << i diff --git a/test/ruby/test_integer.rb b/test/ruby/test_integer.rb index 9a4f560ed5..37eff81005 100644 --- a/test/ruby/test_integer.rb +++ b/test/ruby/test_integer.rb @@ -247,6 +247,10 @@ class Integer;def method_missing(*);"";end;end end; end + def test_exact_p + assert_predicate(1, :exact?) + end + def test_int_p assert_not_predicate(1.0, :integer?) assert_predicate(1, :integer?) diff --git a/test/ruby/test_rational.rb b/test/ruby/test_rational.rb index 301890b620..d1dcceedbb 100644 --- a/test/ruby/test_rational.rb +++ b/test/ruby/test_rational.rb @@ -30,6 +30,10 @@ def test_ratsub assert_equal([true, true], [c.eql?(c1), c1.eql?(c)]) end + def test_exact_p + assert_predicate(Rational(1), :exact?) + end + def test_eql_p c = Rational(0) c2 = Rational(0) -- 2.21.0