basics/floats/numeric_protocol.rb

tests/_examples/basics/floats/numeric_protocol.rb in the metorex repository.

# Numeric carries the protocol every number answers, written in terms of the
# methods a subclass supplies. Here that is `<=>`, `to_f`, `-@`, and `/`.
class Money < Numeric
  attr_reader :cents

  def initialize(cents)
    @cents = cents
  end

  def <=>(other)
    return cents <=> other.cents if other.is_a? Money
    to_f <=> other
  end

  def ==(other)
    other.is_a?(Money) && cents == other.cents
  end

  def -@
    Money.new(-cents)
  end

  def to_f
    cents / 100.0
  end

  def /(other)
    Money.new(cents / other)
  end
end

owed = Money.new -250
p owed.negative?
p owed.positive?
p owed.zero?
p owed.abs.cents
p owed.nonzero?.cents
p Money.new(0).nonzero?.nil?

# The rounding methods read the number through `to_f`.
p Money.new(250).ceil
p Money.new(255).floor
p Money.new(255).round
p Money.new(-255).truncate

# `div` floors the quotient `/` answers, and `divmod` pairs it with what is
# left over.
p Money.new(500).div(2)
p owed.integer?
p owed.real?
p owed.real.cents

# `coerce` answers two of the same class, or two Floats when they differ.
p Money.new(100).coerce(Money.new(200)).map { |m| m.cents }
p Money.new(100).coerce(3)

# `eql?` is equality without conversion, so a different class is never equal.
p Money.new(100).eql? Money.new(100)
p Money.new(100).eql? 1.0

Output:

true
false
false
250
-250
false
3
2
3
-2
2
false
true
-250
[200, 100]
[3.0, 1.0]
true
false