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.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +23 -0
- data/.rspec +1 -0
- data/.travis.yml +24 -0
- data/AUTHORS +126 -0
- data/CHANGELOG.md +619 -0
- data/CONTRIBUTING.md +17 -0
- data/Gemfile +16 -0
- data/LICENSE +23 -0
- data/README.md +438 -0
- data/Rakefile +17 -0
- data/config/currency_backwards_compatible.json +107 -0
- data/config/currency_iso.json +2449 -0
- data/config/currency_non_iso.json +66 -0
- data/lib/money.rb +390 -0
- data/lib/money/allocate.rb +86 -0
- data/lib/money/arithmetic.rb +233 -0
- data/lib/money/bank/base.rb +137 -0
- data/lib/money/bank/single_currency.rb +25 -0
- data/lib/money/bank/variable_exchange.rb +252 -0
- data/lib/money/class_attribute.rb +26 -0
- data/lib/money/currency.rb +402 -0
- data/lib/money/currency/heuristics.rb +150 -0
- data/lib/money/currency/loader.rb +29 -0
- data/lib/money/currency_methods.rb +139 -0
- data/lib/money/formatter.rb +404 -0
- data/lib/money/formatter/to_string.rb +9 -0
- data/lib/money/rates_store/memory.rb +120 -0
- data/lib/money/unaccent.rb +18 -0
- data/lib/money/v6_compatibility.rb +5 -0
- data/lib/money/v6_compatibility/arithmetic.rb +61 -0
- data/lib/money/v6_compatibility/bank_rounding_block.rb +38 -0
- data/lib/money/v6_compatibility/currency_id.rb +29 -0
- data/lib/money/v6_compatibility/format.rb +53 -0
- data/lib/money/v6_compatibility/fractional.rb +74 -0
- data/lib/money/version.rb +3 -0
- data/lib/money2.rb +1 -0
- data/money.gemspec +31 -0
- metadata +207 -0
@@ -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
|