Module: WhittakerTech::Midas::Coin::Arithmetic

Included in:
WhittakerTech::Midas::Coin
Defined in:
app/models/whittaker_tech/midas/coin/arithmetic.rb

Overview

Arithmetic defines the arithmetic and equality semantics of a Coin.

Design invariants

  • Immutable — all operations return new, frozen Coin objects.
  • Closed — operations always return a Coin, never a primitive.
  • Policy-aware only where unavoidable — division accepts a rounding policy; all other operations are exact.
  • Currency-strict — binary operations (+, -, ==) raise ArgumentError when the operands have different currency codes.

Convenience division helpers

For each key in WhittakerTech::Midas::ROUNDING_POLICIES a named shorthand is generated:

coin.divide_round(3)   # same as coin.divide(3, rounding_policy: :round)
coin.divide_ceil(3)
coin.divide_floor(3)
coin.divide_bankers(3)

Since:

  • 0.1.0

Instance Method Summary collapse

Instance Method Details

#%(other) ⇒ Coin

Returns the remainder after dividing by an integer.

Examples:

Coin.value(1001, 'USD') % 3  # => Coin($0.02)  (1001 % 3 == 2 cents)

Parameters:

  • other (Integer)

    the divisor

Returns:

  • (Coin)

    a new, frozen Coin representing the remainder

Raises:

  • (TypeError)

    if other is not an Integer

  • (ZeroDivisionError)

    if other is zero

Since:

  • 0.1.0



109
110
111
112
113
114
# File 'app/models/whittaker_tech/midas/coin/arithmetic.rb', line 109

def %(other)
  raise TypeError unless other.is_a?(Integer)
  raise ZeroDivisionError if other.zero?

  WhittakerTech::Midas::Coin.value(currency_minor % other, currency_code).freeze
end

#*(other) ⇒ Coin

Multiplies the Coin by an integer scalar.

Examples:

Coin.value(500, 'USD') * 3  # => Coin($15.00)

Parameters:

  • other (Integer)

    the multiplier

Returns:

  • (Coin)

    a new, frozen Coin

Raises:

  • (TypeError)

    if other is not an Integer

Since:

  • 0.1.0



94
95
96
97
98
# File 'app/models/whittaker_tech/midas/coin/arithmetic.rb', line 94

def *(other)
  raise TypeError unless other.is_a?(Integer)

  WhittakerTech::Midas::Coin.value(currency_minor * other, currency_code).freeze
end

#+(other) ⇒ Coin

Adds two Coins and returns the sum as a new Coin.

Examples:

a = Coin.value(1000, 'USD')
b = Coin.value(500,  'USD')
a + b  # => Coin($15.00)

Parameters:

  • other (Coin)

    must have the same currency code as the receiver

Returns:

  • (Coin)

    a new, frozen Coin

Raises:

  • (TypeError)

    if other is not a Coin

  • (ArgumentError)

    if the currencies do not match

Since:

  • 0.1.0



65
66
67
68
# File 'app/models/whittaker_tech/midas/coin/arithmetic.rb', line 65

def +(other)
  ensure_same_currency!(other)
  WhittakerTech::Midas::Coin.value(currency_minor + other.currency_minor, currency_code).freeze
end

#-(other) ⇒ Coin

Subtracts another Coin from the receiver and returns the difference.

Examples:

a = Coin.value(1000, 'USD')
b = Coin.value(300,  'USD')
a - b  # => Coin($7.00)

Parameters:

  • other (Coin)

    must have the same currency code as the receiver

Returns:

  • (Coin)

    a new, frozen Coin

Raises:

  • (TypeError)

    if other is not a Coin

  • (ArgumentError)

    if the currencies do not match

Since:

  • 0.1.0



81
82
83
84
# File 'app/models/whittaker_tech/midas/coin/arithmetic.rb', line 81

def -(other)
  ensure_same_currency!(other)
  WhittakerTech::Midas::Coin.value(currency_minor - other.currency_minor, currency_code).freeze
end

#-@Coin

Unary minus operator — shorthand for #negate.

Returns:

Since:

  • 0.1.0



129
130
131
# File 'app/models/whittaker_tech/midas/coin/arithmetic.rb', line 129

def -@
  negate
end

#==(other) ⇒ Boolean Also known as: eql?

Tests value equality with another Coin.

Two Coins are equal when they have the same currency code and the same minor-unit amount. A Coin is never equal to a non-Coin object.

Parameters:

  • other (Object)

    the object to compare

Returns:

  • (Boolean)

Since:

  • 0.1.0



33
34
35
36
37
# File 'app/models/whittaker_tech/midas/coin/arithmetic.rb', line 33

def ==(other)
  other.is_a?(WhittakerTech::Midas::Coin) &&
    currency_code == other.currency_code &&
    currency_minor == other.currency_minor
end

#divide(divisor, rounding_policy: WhittakerTech::Midas::DEFAULT_ROUNDING_POLICY) ⇒ Coin

Divides the Coin by a numeric divisor and returns the quotient.

Division is the only arithmetic operation that requires a rounding policy because dividing integer minor units can produce a non-integer result.

Examples:

Coin.value(1000, 'USD').divide(3, rounding_policy: :ceil)   # => Coin($3.34)
Coin.value(1000, 'USD').divide(3, rounding_policy: :floor)  # => Coin($3.33)
Coin.value(1000, 'USD').divide(3, rounding_policy: :bankers)# => Coin($3.33)

Parameters:

  • divisor (Numeric)

    the divisor; must be non-zero

  • rounding_policy (Symbol) (defaults to: WhittakerTech::Midas::DEFAULT_ROUNDING_POLICY)

    one of the keys in WhittakerTech::Midas::ROUNDING_POLICIES (default: WhittakerTech::Midas::DEFAULT_ROUNDING_POLICY)

Returns:

  • (Coin)

    a new, frozen Coin

Raises:

  • (TypeError)

    if divisor is not Numeric

  • (ZeroDivisionError)

    if divisor is zero

Since:

  • 0.1.0



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'app/models/whittaker_tech/midas/coin/arithmetic.rb', line 150

def divide(divisor, rounding_policy: WhittakerTech::Midas::DEFAULT_ROUNDING_POLICY)
  raise TypeError unless divisor.is_a?(Numeric)
  raise ZeroDivisionError if divisor.zero?

  mode = WhittakerTech::Midas::ROUNDING_POLICIES[rounding_policy]

  unless mode
    warn "Unknown rounding mode: #{rounding_policy.inspect}. Using default rounding mode."
    mode = WhittakerTech::Midas::ROUNDING_POLICIES[
      WhittakerTech::Midas::DEFAULT_ROUNDING_POLICY
    ]
  end

  raw = currency_minor.to_f / divisor
  minor = mode.call(raw).to_i

  WhittakerTech::Midas::Coin.value(minor, currency_code).freeze
end

#hashInteger

Returns a hash value consistent with #eql?.

Coins with the same currency code and minor-unit amount produce the same hash, making them interchangeable as Hash keys or Set members.

Returns:

  • (Integer)

Since:

  • 0.1.0



50
51
52
# File 'app/models/whittaker_tech/midas/coin/arithmetic.rb', line 50

def hash
  [currency_code, currency_minor].hash
end

#negateCoin

Returns a new Coin with the sign reversed.

Examples:

Coin.value(500, 'USD').negate  # => Coin(-$5.00)

Returns:

  • (Coin)

    a new, frozen Coin

Since:

  • 0.1.0



122
123
124
# File 'app/models/whittaker_tech/midas/coin/arithmetic.rb', line 122

def negate
  WhittakerTech::Midas::Coin.value(-currency_minor, currency_code).freeze
end