money2 7.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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