Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I automatically cast a class instance to Float in Ruby?

I have an Angle class that I want to behave like a float, with additional behavior. I have created the class to contain a float and proxy all unknown methods to it:

class Angle
  include Math
  def initialize(angle=0.0)
    @angle = Float(angle)

    # Normalize the angle
    @angle = @angle.modulo(PI*2)
  end

  def to_f
    @angle.to_f
  end

  # Other functionality...

  def method_missing(message, *args, &block)
    if block_given?
      @angle.public_send(message, *args, &block)
    else
      @angle.public_send(message, *args)
    end
  end
end

It works fine. However when I try to use it with trig operations, e.g. Math.cos, I get:

> a = Angle.new(0.0)
 => #<Angle:0x00000000cdb220 @angle=0.0> 
@angle=0.0
> Math.cos(a)
TypeError: can't convert Angle into Float

I know I can use Float(a) to convert to a float, but it's inconvenient since I want this class to behave like a float. Is there a way to automatically convert Angle to float in these cases?

like image 348
Brian Wong Avatar asked Dec 11 '25 19:12

Brian Wong


1 Answers

Looking at the implementation of Math.cos, you can see it calls a macro called Need_Float, which then calls a function rb_to_float. Line 2441 of rb_to_float checks to see if the object passed in is of type Numeric. So it seems the only way to have your own class act as a float in the Math family of functions is to have it inherit from Numeric or a descendant of Numeric. Thus, this modification of your code works as expected:

class Angle < Numeric
  include Math
  def initialize(angle=0.0)
    @angle = Float(angle)

    # Normalize the angle
    @angle = @angle.modulo(PI*2)
  end

  def to_f
    @angle.to_f
  end

  # Other functionality...

  def method_missing(message, *args, &block)
    if block_given?
      @angle.public_send(message, *args, &block)
    else
      @angle.public_send(message, *args)
    end
  end
end

if __FILE__ == $0
  a = Angle.new(0.0)
  p Math.cos(a)
end

I'm not sure what side effects inheriting from Numeric will have, but unfortunately this looks like the only way to have your code work the way you want it to.

like image 144
phiggy Avatar answered Dec 13 '25 12:12

phiggy



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!