money2 7.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,86 @@
1
+ class Money
2
+ module Allocate
3
+ # Allocates money between different parties without losing pennies.
4
+ # After the mathematical split has been performed, leftover pennies will
5
+ # be distributed round-robin amongst the parties. This means that parties
6
+ # listed first will likely receive more pennies than ones that are listed later
7
+ #
8
+ # @param [Array<Numeric>] splits [0.50, 0.25, 0.25] to give 50% of the cash to party1, 25% to party2, and 25% to party3.
9
+ #
10
+ # @return [Array<Money>]
11
+ #
12
+ # @example
13
+ # Money.new(5, "USD").allocate([0.3, 0.7]) #=> [Money.new(2), Money.new(3)]
14
+ # Money.new(100, "USD").allocate([0.33, 0.33, 0.33]) #=> [Money.new(34), Money.new(33), Money.new(33)]
15
+ #
16
+ def allocate(splits)
17
+ allocations = splits.inject(0.to_d) { |sum, n| sum + n }
18
+
19
+ if (allocations - 1) > Float::EPSILON
20
+ raise ArgumentError, "splits add to more then 100%"
21
+ end
22
+
23
+ amounts, left_over = amounts_from_splits(allocations, splits)
24
+
25
+ unless self.class.infinite_precision
26
+ delta = left_over > 0 ? 1 : -1
27
+ # Distribute left over pennies amongst allocations
28
+ left_over.to_i.abs.times { |i| amounts[i % amounts.length] += delta }
29
+ end
30
+
31
+ subunit_to_unit = currency.subunit_to_unit
32
+ amounts.collect { |x| build_new(x.to_d / subunit_to_unit, currency) }
33
+ end
34
+
35
+ # Split money amongst parties evenly without losing pennies.
36
+ #
37
+ # @param [Numeric] num number of parties.
38
+ #
39
+ # @return [Array<Money>]
40
+ #
41
+ # @example
42
+ # Money.new(100, "USD").split(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
43
+ def split(num)
44
+ raise ArgumentError, "need at least one party" if num < 1
45
+
46
+ if self.class.infinite_precision
47
+ split_infinite(num)
48
+ else
49
+ split_flat(num)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def amounts_from_splits(allocations, splits)
56
+ fractional = self.fractional
57
+ left_over = fractional
58
+
59
+ amounts = splits.map do |ratio|
60
+ if self.class.infinite_precision
61
+ fractional * ratio
62
+ else
63
+ (fractional * ratio / allocations).truncate.tap do |frac|
64
+ left_over -= frac
65
+ end
66
+ end
67
+ end
68
+
69
+ [amounts, left_over]
70
+ end
71
+
72
+ def split_infinite(num)
73
+ part = div(num)
74
+ Array.new(num) { part }
75
+ end
76
+
77
+ def split_flat(num)
78
+ subunit_to_unit = currency.subunit_to_unit
79
+ fractional = self.fractional
80
+ low = build_new((fractional / num).to_d / subunit_to_unit, currency)
81
+ high = build_new(low.to_d + 1.to_d / subunit_to_unit, currency)
82
+ remainder = fractional % num
83
+ Array.new(num) { |index| index < remainder ? high : low }
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,233 @@
1
+ class Money
2
+ module Arithmetic
3
+ # Returns a money object with changed polarity.
4
+ #
5
+ # @return [Money]
6
+ #
7
+ # @example
8
+ # - Money.new(100) #=> #<Money @fractional=-100>
9
+ def -@
10
+ build_new(-to_d, currency)
11
+ end
12
+
13
+ # Checks whether two Money objects have the same currency and the same
14
+ # amount. If Money objects have a different currency it will only be true
15
+ # if the amounts are both zero. Checks against objects that are not Money or
16
+ # a subclass will always return false.
17
+ #
18
+ # @param [Money] other Value to compare with.
19
+ #
20
+ # @return [Boolean]
21
+ #
22
+ # @example
23
+ # Money.new(100).eql?(Money.new(101)) #=> false
24
+ # Money.new(100).eql?(Money.new(100)) #=> true
25
+ # Money.new(100, "USD").eql?(Money.new(100, "GBP")) #=> false
26
+ # Money.new(0, "USD").eql?(Money.new(0, "EUR")) #=> true
27
+ # Money.new(100).eql?("1.00") #=> false
28
+ def eql?(other)
29
+ other.is_a?(Money) && (to_d == other.to_d) &&
30
+ (currency == other.currency || to_d == 0)
31
+ end
32
+
33
+ # Compares two Money objects. If money objects have a different currency it
34
+ # will attempt to convert the currency.
35
+ #
36
+ # @param [Money] other Value to compare with.
37
+ #
38
+ # @return [Fixnum]
39
+ #
40
+ # @raise [TypeError] when other object is not Money
41
+ #
42
+ def <=>(other)
43
+ return unless other.is_a?(Money)
44
+ other = other.exchange_to(currency)
45
+ to_d <=> other.to_d
46
+ rescue Money::Bank::UnknownRate
47
+ end
48
+
49
+ # Test if the amount is positive. Returns +true+ if the money amount is
50
+ # greater than 0, +false+ otherwise.
51
+ #
52
+ # @return [Boolean]
53
+ #
54
+ # @example
55
+ # Money.new(1).positive? #=> true
56
+ # Money.new(0).positive? #=> false
57
+ # Money.new(-1).positive? #=> false
58
+ def positive?
59
+ to_d > 0
60
+ end
61
+
62
+ # Test if the amount is negative. Returns +true+ if the money amount is
63
+ # less than 0, +false+ otherwise.
64
+ #
65
+ # @return [Boolean]
66
+ #
67
+ # @example
68
+ # Money.new(-1).negative? #=> true
69
+ # Money.new(0).negative? #=> false
70
+ # Money.new(1).negative? #=> false
71
+ def negative?
72
+ to_d < 0
73
+ end
74
+
75
+ # Returns a new Money object containing the sum of the two operands' monetary
76
+ # values. If +other+ has a different currency then its monetary value
77
+ # is automatically exchanged to this object's currency using +exchange_to+.
78
+ #
79
+ # @param [Money] other Other +Money+ object to add.
80
+ #
81
+ # @return [Money]
82
+ #
83
+ # @example
84
+ # Money.new(100) + Money.new(100) #=> #<Money @amount=200>
85
+ def +(other)
86
+ raise TypeError unless other.is_a?(Money)
87
+ other = other.exchange_to(currency)
88
+ build_new(to_d + other.to_d, currency)
89
+ end
90
+
91
+ # Returns a new Money object containing the difference between the two
92
+ # operands' monetary values. If +other+ has a different currency then
93
+ # its monetary value is automatically exchanged to this object's currency
94
+ # using +exchange_to+.
95
+ #
96
+ # @param [Money] other Other +Money+ object to subtract.
97
+ #
98
+ # @return [Money]
99
+ #
100
+ # @example
101
+ # Money.new(100) - Money.new(99) #=> #<Money @amount=1>
102
+ def -(other)
103
+ raise TypeError unless other.is_a?(Money)
104
+ other = other.exchange_to(currency)
105
+ build_new(to_d - other.to_d, currency)
106
+ end
107
+
108
+ # Multiplies the monetary value with the given number and returns a new
109
+ # +Money+ object with this monetary value and the same currency.
110
+ #
111
+ # Note that you can't multiply a Money object by an other +Money+ object.
112
+ #
113
+ # @param [Numeric] other Number to multiply by.
114
+ #
115
+ # @return [Money] The resulting money.
116
+ #
117
+ # @raise [TypeError] If +other+ is NOT a number.
118
+ #
119
+ # @example
120
+ # Money.new(100) * 2 #=> #<Money @amount=200>
121
+ #
122
+ def *(other)
123
+ raise TypeError unless other.is_a?(Numeric)
124
+ build_new(to_d * other, currency)
125
+ end
126
+
127
+ # Divides the monetary value with the given number and returns a new +Money+
128
+ # object with this monetary value and the same currency.
129
+ # Can also divide by another +Money+ object to get a ratio.
130
+ #
131
+ # +Money/Numeric+ returns +Money+. +Money/Money+ returns +Float+.
132
+ #
133
+ # @param [Money, Numeric] other Number to divide by.
134
+ #
135
+ # @return [Money] The resulting money if you divide Money by a number.
136
+ # @return [Float] The resulting number if you divide Money by a Money.
137
+ #
138
+ # @example
139
+ # Money.new(100) / 10 #=> #<Money @amount=10>
140
+ # Money.new(100) / Money.new(10) #=> 10.0
141
+ #
142
+ def /(other)
143
+ if other.is_a?(Money)
144
+ to_d / other.exchange_to(currency).to_d
145
+ else
146
+ build_new(to_d / other.to_d, currency)
147
+ end
148
+ end
149
+ alias_method :div, :/
150
+
151
+ # Divide money by money or fixnum and return array containing quotient and
152
+ # modulus.
153
+ #
154
+ # @param [Money, Fixnum] other Number to divmod by.
155
+ #
156
+ # @return [Array<Money,Money>,Array<Fixnum,Money>]
157
+ #
158
+ # @example
159
+ # Money.new(100).divmod(9) #=> [#<Money @amount=11>, #<Money @amount=1>]
160
+ # Money.new(100).divmod(Money.new(9)) #=> [11, #<Money @amount=1>]
161
+ def divmod(other)
162
+ if other.is_a?(Money)
163
+ delimiter = other.exchange_to(currency).to_d
164
+ quotient, remainder = to_d.divmod(delimiter)
165
+ [quotient, build_new(remainder, currency)]
166
+ else
167
+ subunit_to_unit = currency.subunit_to_unit
168
+ fractional.divmod(other).map { |x| build_new(x.to_d / subunit_to_unit, currency) }
169
+ end
170
+ end
171
+
172
+ # Equivalent to +divmod(other)[1]+
173
+ #
174
+ # @param [Money, Fixnum] other Number take modulo with.
175
+ #
176
+ # @return [Money]
177
+ #
178
+ # @example
179
+ # Money.new(100).modulo(9) #=> #<Money @amount=1>
180
+ # Money.new(100).modulo(Money.new(9)) #=> #<Money @amount=1>
181
+ def %(other)
182
+ other = other.exchange_to(currency).to_d if other.is_a?(Money)
183
+ build_new(to_d.modulo(other), currency)
184
+ end
185
+ alias_method :modulo, :%
186
+
187
+ # If different signs +modulo(other) - other+ otherwise +modulo(other)+
188
+ #
189
+ # @param [Money, Fixnum] other Number to rake remainder with.
190
+ #
191
+ # @return [Money]
192
+ #
193
+ # @example
194
+ # Money.new(100).remainder(9) #=> #<Money @amount=1>
195
+ def remainder(other)
196
+ other = other.exchange_to(currency).to_d if other.is_a?(Money)
197
+ build_new(to_d.remainder(other), currency)
198
+ end
199
+
200
+ # Return absolute value of self as a new Money object.
201
+ #
202
+ # @return [Money]
203
+ #
204
+ # @example
205
+ # Money.new(-100).abs #=> #<Money @amount=100>
206
+ def abs
207
+ to_d >= 0 ? self : build_new(to_d.abs, currency)
208
+ end
209
+
210
+ # Test if the money amount is zero.
211
+ #
212
+ # @return [Boolean]
213
+ #
214
+ # @example
215
+ # Money.new(100).zero? #=> false
216
+ # Money.new(0).zero? #=> true
217
+ def zero?
218
+ to_d == 0
219
+ end
220
+
221
+ # Test if the money amount is non-zero. Returns this money object if it is
222
+ # non-zero, or nil otherwise, like +Numeric#nonzero?+.
223
+ #
224
+ # @return [Money, nil]
225
+ #
226
+ # @example
227
+ # Money.new(100).nonzero? #=> #<Money @amount=100>
228
+ # Money.new(0).nonzero? #=> nil
229
+ def nonzero?
230
+ to_d != 0 ? self : nil
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,137 @@
1
+ class Money
2
+ # Provides classes that aid in the ability of exchange one currency with
3
+ # another.
4
+ module Bank
5
+
6
+ # The lowest Money::Bank error class.
7
+ # All Money::Bank errors should inherit from it.
8
+ class Error < StandardError
9
+ end
10
+
11
+ # Raised when the bank doesn't know about the conversion rate
12
+ # for specified currencies.
13
+ class UnknownRate < Error
14
+ end
15
+
16
+
17
+ # Money::Bank::Base is the basic interface for creating a money exchange
18
+ # object, also called Bank.
19
+ #
20
+ # A Bank is responsible for storing exchange rates, take a Money object as
21
+ # input and returns the corresponding Money object converted into an other
22
+ # currency.
23
+ #
24
+ # This class exists for aiding in the creating of other classes to exchange
25
+ # money between different currencies. When creating a subclass you will
26
+ # need to implement the following methods to exchange money between
27
+ # currencies:
28
+ #
29
+ # - #exchange_with(Money) #=> Money
30
+ #
31
+ # See Money::Bank::VariableExchange for a real example.
32
+ #
33
+ # Also, you can extend +Money::Bank::VariableExchange+ instead of
34
+ # +Money::Bank::Base+ if your bank implementation needs to store rates
35
+ # internally.
36
+ #
37
+ # @abstract Subclass and override +#exchange_with+ to implement a custom
38
+ # +Money::Bank+ class. You can also override +#setup+ instead of
39
+ # +#initialize+ to setup initial variables, etc.
40
+ class Base
41
+
42
+ # Returns the singleton instance of the Base bank.
43
+ #
44
+ # @return [Money::Bank::Base]
45
+ def self.instance
46
+ @singleton ||= new
47
+ end
48
+
49
+ # The rounding method to use when exchanging rates.
50
+ attr_accessor :rounding_method
51
+
52
+ # Initializes a new +Money::Bank::Base+ object. An optional block can be
53
+ # passed to dictate the rounding method that +#exchange_with+ can use.
54
+ #
55
+ # @yield [n] Optional block to use when rounding after exchanging one
56
+ # currency for another.
57
+ # @yieldparam [Float] n The resulting float after exchanging one currency
58
+ # for another.
59
+ # @yieldreturn [Integer]
60
+ #
61
+ # @return [Money::Bank::Base]
62
+ #
63
+ # @example
64
+ # Money::Bank::Base.new #=> #<Money::Bank::Base @rounding_method=nil>
65
+ # Money::Bank::Base.new {|n|
66
+ # n.floor
67
+ # } #=> #<Money::Bank::Base @round_method=#<Proc>>
68
+ def initialize(&block)
69
+ @rounding_method = block
70
+ setup
71
+ end
72
+
73
+ # Called after initialize. Subclasses can use this method to setup
74
+ # variables, etc that they normally would in +#initialize+.
75
+ #
76
+ # @abstract Subclass and override +#setup+ to implement a custom
77
+ # +Money::Bank+ class.
78
+ #
79
+ # @return [self]
80
+ def setup
81
+ end
82
+
83
+ # Exchanges the given +Money+ object to a new +Money+ object in
84
+ # +to_currency+.
85
+ #
86
+ # @abstract Subclass and override +#exchange_with+ to implement a custom
87
+ # +Money::Bank+ class.
88
+ #
89
+ # @raise NotImplementedError
90
+ #
91
+ # @param [Money] from The +Money+ object to exchange from.
92
+ # @param [Money::Currency, String, Symbol] to_currency The currency
93
+ # string or object to exchange to.
94
+ # @yield [n] Optional block to use to round the result after making
95
+ # the exchange.
96
+ # @yieldparam [Float] n The result after exchanging from one currency to
97
+ # the other.
98
+ # @yieldreturn [Integer]
99
+ #
100
+ # @return [Money]
101
+ def exchange_with(from, to_currency, &block)
102
+ raise NotImplementedError, "#exchange_with must be implemented"
103
+ end
104
+
105
+ # Given two currency strings or object, checks whether they're both the
106
+ # same currency. Return +true+ if the currencies are the same, +false+
107
+ # otherwise.
108
+ #
109
+ # @param [Money::Currency, String, Symbol] currency1 The first currency
110
+ # to compare.
111
+ # @param [Money::Currency, String, Symbol] currency2 The second currency
112
+ # to compare.
113
+ #
114
+ # @return [Boolean]
115
+ #
116
+ # @example
117
+ # same_currency?("usd", "USD") #=> true
118
+ # same_currency?("usd", "EUR") #=> false
119
+ # same_currency?("usd", Currency.new("USD")) #=> true
120
+ # same_currency?("usd", "USD") #=> true
121
+ def same_currency?(currency1, currency2)
122
+ Currency.wrap(currency1) == Currency.wrap(currency2)
123
+ end
124
+
125
+ # Rounds value with a given block or rounding_method.
126
+ def round(value, currency, mode = nil, &block)
127
+ method = block || mode || rounding_method
128
+ return value unless method
129
+ if method.is_a?(Symbol)
130
+ value.round(currency.decimal_places, method)
131
+ else
132
+ method.call(value, currency)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end