/
arithmetic.cr
181 lines (165 loc) · 5 KB
/
arithmetic.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
struct Money
module Arithmetic
# Returns `true` if the money amount is greater than 0, `false` otherwise.
#
# ```
# Money.new(1).positive? # => true
# Money.new(0).positive? # => false
# Money.new(-1).positive? # => false
# ```
def positive?
amount > 0
end
# Returns `true` if the money amount is less than 0, `false` otherwise.
#
# ```
# Money.new(-1).negative? # => true
# Money.new(0).negative? # => false
# Money.new(1).negative? # => false
# ```
def negative?
amount < 0
end
# Returns `true` if the money amount is zero.
#
# ```
# Money.new(0).zero? # => true
# Money.new(100).zero? # => false
# Money.new(-100).zero? # => false
# ```
def zero?
amount == 0
end
# Returns absolute value of `self` as a new `Money` object.
#
# ```
# Money.new(-100).abs # => Money(@amount=1)
# ```
def abs : Money
copy_with(amount: amount.abs)
end
# Alias of `#abs`.
#
# ```
# +Money.new(-100) # => Money(@amount=1)
# ```
def + : Money
abs
end
# Returns a new `Money` object with changed polarity.
#
# ```
# -Money.new(100) # => Money(@amount=-1)
# ```
def - : Money
copy_with(amount: -amount)
end
# Returns a new `Money` object containing the sum of the two
# operands' monetary values.
#
# ```
# Money.new(100) + Money.new(100) # => Money(@amount=2)
# ```
def +(other : Money) : Money
return self if other.zero?
with_same_currency(other) do |converted_other|
copy_with(amount: amount + converted_other.amount)
end
end
# Returns a new `Money` object containing the difference between the two
# operands' monetary values.
#
# ```
# Money.new(100) - Money.new(99) # => Money(@amount=0.01)
# ```
def -(other : Money) : Money
return self if other.zero?
with_same_currency(other) do |converted_other|
copy_with(amount: amount - converted_other.amount)
end
end
# Multiplies the monetary value with the given *other* `Number` and returns
# a new `Money` object with this monetary value and the same `#currency`.
#
# ```
# Money.new(100) * 2 # => Money(@amount=2)
# ```
def *(other : Number) : Money
copy_with(amount: amount * other)
end
# Divides the monetary value with the given *other* `Number` and returns
# a new `Money` object with this monetary value and the same `#currency`.
#
# ```
# Money.new(100) / 10 # => Money(@amount=0.1)
# ```
def /(other : Number) : Money
copy_with(amount: amount / other)
end
# Divides the monetary value with the given *other* `Money` object and
# returns a ratio.
#
# ```
# Money.new(100) / Money.new(10) # => 10.0
# ```
def /(other : Money) : BigDecimal
with_same_currency(other) do |converted_other|
amount / converted_other.amount
end
end
# Divide by `Money` or `Number` and return `Tuple` containing
# quotient and modulus.
#
# ```
# Money.new(100).divmod(9) # => {Money(@amount=0.11), Money(@amount=0.01)}
# Money.new(100).divmod(Money.new(9)) # => {11, Money(@amount=0.01)}
# ```
def divmod(other : Money) : {BigInt, Money}
with_same_currency(other) do |converted_other|
quotient, remainder = fractional.divmod(converted_other.fractional)
{quotient, copy_with(fractional: remainder)}
end
end
# :ditto:
def divmod(other : Number) : {Money, Money}
quotient, remainder = fractional.divmod(other.to_big_i)
{copy_with(fractional: quotient), copy_with(fractional: remainder)}
end
# Equivalent to `#divmod(other)[1]`.
#
# ```
# Money.new(100).modulo(9) # => Money(@amount=0.01)
# Money.new(100).modulo(Money.new(9)) # => Money(@amount=0.01)
# ```
def modulo(other) : Money
divmod(other)[1]
end
# Alias of `#modulo`.
def %(other) : Money
modulo(other)
end
# If different signs `#modulo(other) - other`, otherwise `#modulo(other)`.
#
# ```
# Money.new(100).remainder(9) # => Money(@amount=0.01)
# ```
def remainder(other : Number) : Money
if (amount < 0 && other < 0) || (amount > 0 && other > 0)
modulo(other)
else
modulo(other) - copy_with(amount: other)
end
end
# Rounds the monetary amount to smallest unit of coinage, using
# rounding *mode* if given, or `Money.rounding_mode` otherwise.
#
# ```
# Money.new(10.1, "USD").round # => Money(@amount=10, @currency="USD")
# Money.new(10.5, "USD").round(mode: :ties_even) # => Money(@amount=10, @currency="USD")
# Money.new(10.5, "USD").round(mode: :ties_away) # => Money(@amount=11, @currency="USD")
# ```
def round(precision : Int = 0, mode : Number::RoundingMode = Money.rounding_mode) : Money
copy_with(amount: @amount.round(precision, mode: mode))
end
end
end