summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2019-06-04 21:41:02 -0700
committerJeremy Evans <code@jeremyevans.net>2019-06-19 10:50:58 -0700
commitb9ef35e4c6325864e013ab6e45df6fe00f759a47 (patch)
tree0e3814c316dd16034139a4b2802a69aca469da7c
parent65944e96d39a8ae3602751f49cb337335b2c6c45 (diff)
Implement Complex#<=>
Implement Complex#<=> so that it is usable as an argument when calling <=> on objects of other classes (since #coerce will coerce such numbers to Complex). If the complex number has a zero imaginary part, and the other argument is a real number (or complex number with zero imaginary part), return -1, 0, or 1. Otherwise, return nil, indicating the objects are not comparable. Fixes [Bug #15857]
-rw-r--r--NEWS7
-rw-r--r--complex.c47
-rw-r--r--spec/ruby/core/complex/spaceship_spec.rb27
-rw-r--r--test/ruby/test_complex.rb14
-rw-r--r--test/ruby/test_complexrational.rb8
5 files changed, 90 insertions, 13 deletions
diff --git a/NEWS b/NEWS
index 0f612533ff..ef5b30151b 100644
--- a/NEWS
+++ b/NEWS
@@ -62,6 +62,13 @@ sufficient information, see the ChangeLog file or Redmine
=== Core classes updates (outstanding ones only)
+Complex::
+
+ New method::
+
+ * Added Complex#<=>. So 0 <=> 0i will not raise NoMethodError.
+ [Bug #15857]
+
Enumerable::
New method::
diff --git a/complex.c b/complex.c
index aa905c1c88..db94797a58 100644
--- a/complex.c
+++ b/complex.c
@@ -1021,14 +1021,50 @@ nucomp_eqeq_p(VALUE self, VALUE other)
return f_boolcast(f_eqeq_p(other, self));
}
+static VALUE
+nucomp_real_p(VALUE self)
+{
+ get_dat1(self);
+ return(f_zero_p(dat->imag) ? Qtrue : Qfalse);
+}
+
+/*
+ * call-seq:
+ * cmp <=> object -> 0, 1, -1, or nil
+ *
+ * If +cmp+'s imaginary part is zero, and +object+ is also a
+ * real number (or a Complex number where the imaginary part is zero),
+ * compare the real part of +cmp+ to object. Otherwise, return nil.
+ *
+ * Complex(2, 3) <=> Complex(2, 3) #=> nil
+ * Complex(2, 3) <=> 1 #=> nil
+ * Complex(2) <=> 1 #=> 1
+ * Complex(2) <=> 2 #=> 0
+ * Complex(2) <=> 3 #=> -1
+ */
+static VALUE
+nucomp_cmp(VALUE self, VALUE other)
+{
+ if (nucomp_real_p(self) && k_numeric_p(other)) {
+ if (RB_TYPE_P(other, T_COMPLEX) && nucomp_real_p(other)) {
+ get_dat2(self, other);
+ return rb_funcall(adat->real, idCmp, 1, bdat->real);
+ } else if (f_real_p(other)) {
+ get_dat1(self);
+ return rb_funcall(dat->real, idCmp, 1, other);
+ }
+ }
+ return Qnil;
+}
+
/* :nodoc: */
static VALUE
nucomp_coerce(VALUE self, VALUE other)
{
- if (k_numeric_p(other) && f_real_p(other))
- return rb_assoc_new(f_complex_new_bang1(CLASS_OF(self), other), self);
if (RB_TYPE_P(other, T_COMPLEX))
return rb_assoc_new(other, self);
+ if (k_numeric_p(other) && f_real_p(other))
+ return rb_assoc_new(f_complex_new_bang1(CLASS_OF(self), other), self);
rb_raise(rb_eTypeError, "%"PRIsVALUE" can't be coerced into %"PRIsVALUE,
rb_obj_class(other), rb_obj_class(self));
@@ -1147,9 +1183,10 @@ rb_complex_conjugate(VALUE self)
/*
* call-seq:
- * cmp.real? -> false
+ * Complex(1).real? -> false
+ * Complex(1, 2).real? -> false
*
- * Returns false.
+ * Returns false, even if the complex number has no imaginary part.
*/
static VALUE
nucomp_false(VALUE self)
@@ -2228,7 +2265,6 @@ Init_Complex(void)
rb_undef_methods_from(rb_cComplex, rb_mComparable);
rb_undef_method(rb_cComplex, "%");
- rb_undef_method(rb_cComplex, "<=>");
rb_undef_method(rb_cComplex, "div");
rb_undef_method(rb_cComplex, "divmod");
rb_undef_method(rb_cComplex, "floor");
@@ -2254,6 +2290,7 @@ Init_Complex(void)
rb_define_method(rb_cComplex, "**", rb_complex_pow, 1);
rb_define_method(rb_cComplex, "==", nucomp_eqeq_p, 1);
+ rb_define_method(rb_cComplex, "<=>", nucomp_cmp, 1);
rb_define_method(rb_cComplex, "coerce", nucomp_coerce, 1);
rb_define_method(rb_cComplex, "abs", rb_complex_abs, 0);
diff --git a/spec/ruby/core/complex/spaceship_spec.rb b/spec/ruby/core/complex/spaceship_spec.rb
new file mode 100644
index 0000000000..7b2849a86a
--- /dev/null
+++ b/spec/ruby/core/complex/spaceship_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+
+describe "Complex#<=>" do
+ ruby_version_is '2.7' do
+ it "returns nil if either self or argument has imaginary part" do
+ (Complex(5, 1) <=> Complex(2)).should be_nil
+ (Complex(1) <=> Complex(2, 1)).should be_nil
+ (5 <=> Complex(2, 1)).should be_nil
+ end
+
+ it "returns nil if argument is not numeric" do
+ (Complex(5, 1) <=> "cmp").should be_nil
+ (Complex(1) <=> "cmp").should be_nil
+ (Complex(1) <=> Object.new).should be_nil
+ end
+
+ it "returns 0, 1, or -1 if self and argument do not have imaginary part" do
+ (Complex(5) <=> Complex(2)).should == 1
+ (Complex(2) <=> Complex(3)).should == -1
+ (Complex(2) <=> Complex(2)).should == 0
+
+ (Complex(5) <=> 2).should == 1
+ (Complex(2) <=> 3).should == -1
+ (Complex(2) <=> 2).should == 0
+ end
+ end
+end
diff --git a/test/ruby/test_complex.rb b/test/ruby/test_complex.rb
index 3da66890c1..60a46d737d 100644
--- a/test/ruby/test_complex.rb
+++ b/test/ruby/test_complex.rb
@@ -518,9 +518,16 @@ class Complex_Test < Test::Unit::TestCase
end
def test_cmp
- assert_raise(NoMethodError){1 <=> Complex(1,1)}
- assert_raise(NoMethodError){Complex(1,1) <=> 1}
- assert_raise(NoMethodError){Complex(1,1) <=> Complex(1,1)}
+ assert_nil(Complex(5, 1) <=> Complex(2))
+ assert_nil(5 <=> Complex(2, 1))
+
+ assert_equal(1, Complex(5) <=> Complex(2))
+ assert_equal(-1, Complex(2) <=> Complex(3))
+ assert_equal(0, Complex(2) <=> Complex(2))
+
+ assert_equal(1, Complex(5) <=> 2)
+ assert_equal(-1, Complex(2) <=> 3)
+ assert_equal(0, Complex(2) <=> 2)
end
def test_eqeq
@@ -891,7 +898,6 @@ class Complex_Test < Test::Unit::TestCase
def test_respond
c = Complex(1,1)
assert_not_respond_to(c, :%)
- assert_not_respond_to(c, :<=>)
assert_not_respond_to(c, :div)
assert_not_respond_to(c, :divmod)
assert_not_respond_to(c, :floor)
diff --git a/test/ruby/test_complexrational.rb b/test/ruby/test_complexrational.rb
index 0360f5ee42..bf4e2b1809 100644
--- a/test/ruby/test_complexrational.rb
+++ b/test/ruby/test_complexrational.rb
@@ -102,7 +102,7 @@ class ComplexRational_Test < Test::Unit::TestCase
assert_equal(Complex(SimpleRat(4,3),SimpleRat(1,1)), c * 2)
assert_equal(Complex(SimpleRat(1,3),SimpleRat(1,4)), c / 2)
assert_equal(Complex(SimpleRat(7,36),SimpleRat(2,3)), c ** 2)
- assert_raise(NoMethodError){c <=> 2}
+ assert_nil(c <=> 2)
assert_equal(Complex(SimpleRat(8,3),SimpleRat(1,2)), 2 + c)
assert_equal(Complex(SimpleRat(4,3),SimpleRat(-1,2)), 2 - c)
@@ -111,7 +111,7 @@ class ComplexRational_Test < Test::Unit::TestCase
r = 2 ** c
assert_in_delta(1.4940, r.real, 0.001)
assert_in_delta(0.5392, r.imag, 0.001)
- assert_raise(NoMethodError){2 <=> c}
+ assert_nil(2 <=> c)
assert_equal(Complex(SimpleRat(13,6),SimpleRat(5,2)), c + cc)
assert_equal(Complex(SimpleRat(-5,6),SimpleRat(-3,2)), c - cc)
@@ -120,7 +120,7 @@ class ComplexRational_Test < Test::Unit::TestCase
r = c ** cc
assert_in_delta(0.1732, r.real, 0.001)
assert_in_delta(0.1186, r.imag, 0.001)
- assert_raise(NoMethodError){c <=> cc}
+ assert_nil(c <=> cc)
assert_equal(Complex(SimpleRat(13,6),SimpleRat(5,2)), cc + c)
assert_equal(Complex(SimpleRat(5,6),SimpleRat(3,2)), cc - c)
@@ -129,7 +129,7 @@ class ComplexRational_Test < Test::Unit::TestCase
r = cc ** c
assert_in_delta(0.5498, r.real, 0.001)
assert_in_delta(1.0198, r.imag, 0.001)
- assert_raise(NoMethodError){cc <=> c}
+ assert_nil(cc <=> c)
assert_equal([SimpleRat,SimpleRat],
(+c).instance_eval{[real.class, imag.class]})