0

I have a class MoneyBox with two fields (wallet and cent). I need to overload operations +, -, and * with these fields. How to correctly implement the overloading of these operators with two parameters?

class MoneyBox
  attr_accessor :wallet, :cent

  def initialize(wallet, cent)
  @wallet = wallet  
  @cent = cent 
   
  end

  def + (obj, obj_cent)
    self.wallet = self.wallet + obj.wallet
    self.cent = self.cent + obj_cent.cent
    return self.wallet, self.cent 
  end

  def - (obj, obj_cent) 
    self.wallet = self.wallet - obj.wallet
    self.cent = self.cent - obj_cent.cent
    return self.wallet, self.cent 
  end

  def * (obj,obj_cent) 
    self.wallet = self.wallet * obj.wallet
    self.cent = self.cent * obj_cent
    return self.wallet, self.cent 
  end

end

It should be something like this:

Cash1 = MoneyBox.new(500, 30)
Cash2 = MoneyBox.new(100, 15)

puts " D1 = #{D1 = (Cash1-(Cash2*2))}" # 500,30 - 100,15*2 = 300,0
1
  • @KirKon753: I wouldn't call this overloading, but defining an operator for a class. In your approach, i.e. the + operator seems to deal with three operands (self, and the two arguments obj and obj_cent), and this is not possible, since + , no matter how you redefine it, can syntactically have only two operands. Commented May 26, 2021 at 7:11

3 Answers 3

1

You already have a class that encapsulates wallet / cent pairs and the operations are also performed pair-wise. Why not take advantage of it and make +, - and * take a (single) MoneyBox instance as their argument and return the result as another MoneyBox instance, e.g.: (arithmetic operators shouldn't modify their operands)

class MoneyBox
  attr_accessor :wallet, :cent

  def initialize(wallet, cent)
    @wallet = wallet
    @cent = cent
  end

  def +(other)
    MoneyBox.new(wallet + other.wallet, cent + other.cent)
  end

  def -(other)
    MoneyBox.new(wallet - other.wallet, cent - other.cent)
  end

  def *(other)
    MoneyBox.new(wallet * other.wallet, cent * other.cent)
  end

  def to_s
    "#{wallet},#{cent}"
  end
end

Example usage:

cash1 = MoneyBox.new(500, 30)
cash2 = MoneyBox.new(100, 15)

puts "#{cash1} + #{cash2} = #{cash1 + cash2}"
# 500,30 + 100,15 = 600,45

puts "#{cash1} - #{cash2} = #{cash1 - cash2}"
# 500,30 - 100,15 = 400,15

puts "#{cash1} * #{cash2} = #{cash1 * cash2}"
# 500,30 * 100,15 = 50000,45

To multiply both wallet and cents by 2 you'd use a MoneyBox.new(2, 2) instance:

puts "#{cash1} - #{cash2} * 2 = #{cash1 - cash2 * MoneyBox.new(2, 2)}"
# 500,30 - 100,15 * 2 = 300,0

Note that operator precedence still applies, so the result is evaluated as cash1 - (cash2 * Money.new(2, 2)).

If you want to multiply by integers directly, i.e. without explicitly creating that MoneyBox.new(2, 2) instance, you could move that logic into * by adding a conditional, e.g:

  def *(other)
    case other
    when Integer
      MoneyBox.new(wallet * other, cent * other)
    when MoneyBox
      MoneyBox.new(wallet * other.wallet, cent * other.cent)
    else
      raise ArgumentError, "expected Integer or MoneyBox, got #{other.class}"
    end
  end

Which gives you:

cash1 = MoneyBox.new(500, 30)
cash2 = MoneyBox.new(100, 15)

puts "#{cash1} - #{cash2} * 2 = #{cash1 - cash2 * 2}"
# 500,30 - 100,15 * 2 = 300,0

Note that this only defines MoneyBox#* and doesn't alter Integer#*, so 2 * cash2 does not work by default:

2 * cash2
# TypeError: MoneyBox can't be coerced into Integer

That's because you're calling * on 2 and Integer doesn't know how to deal with a MoneyBox instance. This could be fixed by implementing coerce in MoneyBox: (something that only works for numerics)

  def coerce(other)
    [self, other]
  end

Which effectively turns 2 * cash2 into cash2 * 2.


BTW if you always call * with an integer and there's no need to actually multiply two MoneyBox instances, it would of course get much simpler:

  def *(factor)
    MoneyBox.new(wallet * factor, cent * factor)
  end
Sign up to request clarification or add additional context in comments.

Comments

1

You can't use the typical x + y syntax if you're going to overload the operator to take two arguments.

Instead, you have to use .+, see the following:

class Foo
  def initialize(val)
    @val = val
  end
   
  def +(a,b)
    @val + a + b
  end
end

foo = Foo.new(1)

foo.+(2,3)
# => 6

Comments

1

Your example usage does not match the method definition. You want to use your objects in the way to write expressions like i.e. Cash2 * 2). Since Cash2 is a constant of class MoneyBox, the definition for the multiplication by a scalar would be something like

def * (factor)
  self.class.new(wallet, cent * factor)
end

This is a shortcut of

def * (factor)
  MoneyBox.new(self.wallet, self.cent * factor)
end

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.