Complex<MyNumeric,MyNumeric> replaced Complex<MyNumeric,Float>; How to use coerce()

A MyNumeric type object that inherits Numeric can be specified in the Ruby Complex class constructor.

However, when I specified a negative value of MyNumber type for the imaginary part, the imaginary part was replaced with a Float type.

Maybe I’m missing a usage of coerce() methods.
Please tell me how to use coerce correctly.

–(1) Example that worked as expected – “Complex<Rational,BigDecaimal.negative>>”;

p   Complex(Rational(1,2), Rational(-34567890123456789,5))
# =>  ((1/2)-(34567890123456789/5)*i)

–(2) Example that worked as expected – “Complex<MyNumber,MyNumber.positive>>”,;

p  y=Complex(MyNumeric.new(1,1,5), MyNumeric.new(3,1,7))
 #=> ((1 + 1/2 + 5*√(3)/2)+(3 + 1/2 + 7*√(3)/2)*i)
p y.imag.class
 #=> MyNumeric

– (3) Examples of unexpected behavior;

p  z= y.conjugate, z.imag.class
 #=> -((1 + 1/2 + 5*√(3)/2)-9.56217782649107i) 
  #=> Float  <--- unexpected behavior;
p  z=Complex(MyNumeric.new(1,1,5), MyNumeric.new(-9,1,7)), .class
 #=> ((1 + 1/2 + 5*√(3)/2)-5.901923788646684i) 
 #=> (-9 + 1/2 + 3*√(3)/2)
 #=> MyNumeric
p 0 - z.imag
 #=> 5.901923788646684   <--- Integer.coerce (MyNumeric)  -> Float ?

– The definition of MyNumeric is as follows;

class MyNumeric < Numeric
    attr_accessor :num, :rt3h,:hf
    CONST_Sqrt3H = Math.sqrt(3)/2 
    CONST_hf = 0.5
    def initialize(a,b = 0,c = 0)
        if a.is_a?(MyNumeric) && (b == 0) && (c == 0)
            @num, @hf = a.num + (a.hf / 2), a.hf % 2
            @rt3h = a.rt3h
        elsif a.is_a?(Integer) && b.is_a?(Integer) && c.is_a?(Integer)
            @num, @hf = a + (b / 2), b % 2
            @rt3h = c
        else
            $stderr.puts("ERROR at MyNumeric") 
        end
    end
    def +(other)
        if other.is_a?(MyNumeric)
            return new MyNumeric(@num + other.num, @hf + other.hf, @rt3h + other.rt3 )
        elsif other.is_a?(Integer)
            return new MyNumeric(@num + other, @rt3h,@hf)
        end
    end
    def -(other)
        if other.is_a?(MyNumeric)
            return new MyNumeric(@num - other.num, @hf - other.hf, @rt3h - other.rt3 )
        elsif other.is_a?(Integer)
            return new MyNumeric(@num - other, @rt3h,@hf)
        end
    end
    def zero?()
        @num.zero?() && @hf.zero?() &&  @rt3h.zero?
    end 
    def ==(other)
        if other.is_a?(MyNumeric)
            return (@num ==  other.num) && (@rt3h == other.rt3h) && (@hf == other.hf )
        elsif other.is_a?(Integer) && (@rt3h == 0) && (@hf == 0)
            return @num == other
        else
            return false
        end
    end
    def negative?()
        self.to_f.negative?
    end
    def <=>(other)
        return self.to_f() <=> other.to_f
    end
    def to_f()
        return @num + @rt3h*CONST_Sqrt3H + @hf * CONST_hf
    end
    def inspect()
        return "(" + [num != 0 ? num.to_s() : "0", @hf != 0 ? @hf.to_s() + "/2" : nil, @rt3h != 0 ? @rt3h.to_s() + "*√(3)/2" : nil].filter{|c| !c.nil?}.to_a.join(" + ") + ")"
    end
    def to_s()
        return "(" + [num != 0 ? num.to_s() : "0", @hf != 0 ? @hf.to_s() + "/2" : nil, @rt3h != 0 ? @rt3h.to_s() + "*√(3)/2" : nil].filter{|c| !c.nil?}.to_a.join(" + ") + ")"
    end
    def coerce(other)
        if other.is_a?(Numeric)
          return [self.to_f, other]
        else
          super
        end
    end
end
# builtin coerce ???
def coerce(other)
    if other.is_a?(Numeric)
      return [self.to_f, other]
    else
      super
    end
end
# end of builtin coerce ???

Your MyNumeric class needs a coerce method to properly interact with Ruby’s built-in numeric classes. The coerce method is used when an arithmetic operation can’t be performed because of incompatible classes.

In your case, you need to define it like this:

def coerce(other)
  if other.is_a?(Numeric)
    return [self.to_f, other]
  else
    super
  end
end

This method attempts to convert the other value to a Float, which will allow arithmetic operations with your MyNumeric objects.

Try this and see if it works.

Thanks for the reply.
I added the specified piece of code,
but again I got unintended results.

see updated “The definition of MyNumeric” as above.

I could improve the code just a little bit.
But the sign is wrong when the imaginary part shows a negative value.
(a + (negative value of b) *i) displayed as (a - (negative value of b) *i).

Is there anything else missing?

I’m concerned about something not firing (coerce() and more),
for example when expressions such as (Integer(0) - MyNumeric) are computed behind Complex.conjugate() or Complex.inspect().

p  "z2=",z=Complex(MyNumeric.new(1,1,5), MyNumeric.new(-9,1,3)),z.imag, z.imag.class
#=> ((1 + 1/2 + 5*√(3)/2)-(-9 + 1/2 + 3*√(3)/2)*i)  <-- (a **-** (negative value of b) *i)
#=> (-9 + 1/2 + 3*√(3)/2)
#=> MyNumeric
class MyNumeric < Numeric
    attr_accessor :num, :rt3h,:hf
    CONST_Sqrt3H = Math.sqrt(3)/2 
    CONST_hf = 0.5
    def initialize(a,b = 0,c = 0)
        if a.is_a?(MyNumeric) && (b == 0) && (c == 0)
            @num, @hf = a.num + (a.hf / 2), a.hf % 2
            @rt3h = a.rt3h
        elsif a.is_a?(Integer) && b.is_a?(Integer) && c.is_a?(Integer)
            @num, @hf = a + (b / 2), b % 2
            @rt3h = c
        else
            $stderr.puts("ERROR at MyNumeric") 
        end
    end
    def +(other)
        if other.is_a?(MyNumeric)
            return MyNumeric.new(@num + other.num, @hf + other.hf, @rt3h + other.rt3 )
        elsif other.is_a?(Integer)
            return MyNumeric.new(@num + other, @rt3h,@hf)
        end
    end
    def -(other)
        if other.is_a?(MyNumeric)
            return MyNumeric.new(@num - other.num, @hf - other.hf, @rt3h - other.rt3h )
        elsif other.is_a?(Integer)
            return MyNumeric.new(@num - other, @hf,  @rt3h)
        end
    end
    def zero?()
        @num.zero?() && @hf.zero?() &&  @rt3h.zero?
    end 
    def ==(other)
        if other.is_a?(MyNumeric)
            return (@num ==  other.num) && (@rt3h == other.rt3h) && (@hf == other.hf )
        elsif other.is_a?(Integer) && (@rt3h == 0) && (@hf == 0)
            return @num == other
        else
            return false
        end
    end
    def negative?()
        self.to_f.negative?
    end
    def <=>(other)
        return self.to_f() <=> other.to_f
    end
    def to_f()
        return @num + @rt3h*CONST_Sqrt3H + @hf * CONST_hf
    end
    def inspect()
        return "(" + [num != 0 ? num.to_s() : "0", @hf != 0 ? @hf.to_s() + "/2" : nil, @rt3h != 0 ? @rt3h.to_s() + "*√(3)/2" : nil].filter{|c| !c.nil?}.to_a.join(" + ") + ")"
    end
    def to_s()
        return "(" + [num != 0 ? num.to_s() : "0", @hf != 0 ? @hf.to_s() + "/2" : nil, @rt3h != 0 ? @rt3h.to_s() + "*√(3)/2" : nil].filter{|c| !c.nil?}.to_a.join(" + ") + ")"
    end
    def coerce(other)
        if other.is_a?(MyNumeric)  ## add this
            super
        elsif other.is_a?(Numeric)
            return [self, MyNumeric.new(other)] ## change this
        else
          super
        end
    end
end