money 3.7.1 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/CHANGELOG.md +384 -351
  2. data/LICENSE +21 -21
  3. data/README.md +243 -214
  4. data/Rakefile +49 -49
  5. data/lib/money.rb +28 -27
  6. data/lib/money/bank/base.rb +131 -131
  7. data/lib/money/bank/variable_exchange.rb +252 -252
  8. data/lib/money/core_extensions.rb +82 -82
  9. data/lib/money/currency.rb +263 -422
  10. data/lib/money/currency_loader.rb +19 -0
  11. data/lib/money/money.rb +405 -405
  12. data/lib/money/money/arithmetic.rb +246 -246
  13. data/lib/money/money/formatting.rb +260 -244
  14. data/lib/money/money/parsing.rb +350 -350
  15. data/money.gemspec +29 -35
  16. data/spec/bank/base_spec.rb +72 -72
  17. data/spec/bank/variable_exchange_spec.rb +238 -238
  18. data/spec/core_extensions_spec.rb +158 -158
  19. data/spec/currency_spec.rb +120 -133
  20. data/spec/money/arithmetic_spec.rb +479 -479
  21. data/spec/money/formatting_spec.rb +383 -375
  22. data/spec/money/parsing_spec.rb +197 -197
  23. data/spec/money_spec.rb +292 -292
  24. data/spec/spec_helper.rb +28 -28
  25. metadata +54 -126
  26. data/lib/money.rbc +0 -184
  27. data/lib/money/bank/base.rbc +0 -818
  28. data/lib/money/bank/variable_exchange.rbc +0 -2550
  29. data/lib/money/core_extensions.rbc +0 -664
  30. data/lib/money/currency.rbc +0 -22708
  31. data/lib/money/money.rbc +0 -3861
  32. data/lib/money/money/arithmetic.rbc +0 -2778
  33. data/lib/money/money/formatting.rbc +0 -2265
  34. data/lib/money/money/parsing.rbc +0 -2737
  35. data/spec/bank/base_spec.rbc +0 -2461
  36. data/spec/bank/variable_exchange_spec.rbc +0 -7541
  37. data/spec/core_extensions_spec.rbc +0 -5921
  38. data/spec/currency_spec.rbc +0 -4535
  39. data/spec/money/arithmetic_spec.rbc +0 -25140
  40. data/spec/money/formatting_spec.rbc +0 -12545
  41. data/spec/money/parsing_spec.rbc +0 -6511
  42. data/spec/money_spec.rbc +0 -9824
  43. data/spec/spec_helper.rbc +0 -575
@@ -0,0 +1,19 @@
1
+ require 'pathname'
2
+
3
+ module CurrencyLoader
4
+ extend self
5
+
6
+ DATA_PATH = Pathname.new(__FILE__).dirname + "../../config/"
7
+
8
+ # Loads and returns the currencies stored in JSON files in the config directory.
9
+ #
10
+ # @return [Hash]
11
+ def load_currencies
12
+ json = File.read(DATA_PATH + 'currency.json')
13
+ currencies = JSON.parse(json, :symbolize_names => true)
14
+
15
+ # merge the currencies kept for backwards compatibility
16
+ json = File.read(DATA_PATH + 'currency_bc.json')
17
+ currencies.merge!(JSON.parse(json, :symbolize_names => true))
18
+ end
19
+ end
data/lib/money/money.rb CHANGED
@@ -1,405 +1,405 @@
1
- # encoding: utf-8
2
- require 'money/bank/variable_exchange'
3
- require 'money/money/arithmetic'
4
- require 'money/money/parsing'
5
- require 'money/money/formatting'
6
-
7
- # Represents an amount of money in a given currency.
8
- class Money
9
- include Comparable
10
- include Arithmetic
11
- include Formatting
12
- include Parsing
13
-
14
- # The value of the money in cents.
15
- #
16
- # @return [Integer]
17
- attr_reader :cents
18
-
19
- # The currency the money is in.
20
- #
21
- # @return [Currency]
22
- attr_reader :currency
23
-
24
- # The +Money::Bank+ based object used to perform currency exchanges with.
25
- #
26
- # @return [Money::Bank::*]
27
- attr_reader :bank
28
-
29
- # Class Methods
30
- class << self
31
- # Each Money object is associated to a bank object, which is responsible
32
- # for currency exchange. This property allows you to specify the default
33
- # bank object. The default value for this property is an instance if
34
- # +Bank::VariableExchange.+ It allows one to specify custom exchange rates.
35
- #
36
- # @return [Money::Bank::*]
37
- attr_accessor :default_bank
38
-
39
- # The default currency, which is used when +Money.new+ is called without an
40
- # explicit currency argument. The default value is Currency.new("USD"). The
41
- # value must be a valid +Money::Currency+ instance.
42
- #
43
- # @return [Money::Currency]
44
- attr_accessor :default_currency
45
-
46
- # Use this to disable i18n even if it's used by other objects in your app.
47
- #
48
- # @return [true,false]
49
- attr_accessor :use_i18n
50
- end
51
-
52
- # Set the default bank for creating new +Money+ objects.
53
- self.default_bank = Bank::VariableExchange.instance
54
-
55
- # Set the default currency for creating new +Money+ object.
56
- self.default_currency = Currency.new("USD")
57
-
58
- # Default to using i18n
59
- self.use_i18n = true
60
-
61
- # Create a new money object with value 0.
62
- #
63
- # @param [Currency, String, Symbol] currency The currency to use.
64
- #
65
- # @return [Money]
66
- #
67
- # @example
68
- # Money.empty #=> #<Money @cents=0>
69
- def self.empty(currency = default_currency)
70
- Money.new(0, currency)
71
- end
72
-
73
- # Creates a new Money object of the given value, using the Canadian
74
- # dollar currency.
75
- #
76
- # @param [Integer] cents The cents value.
77
- #
78
- # @return [Money]
79
- #
80
- # @example
81
- # n = Money.ca_dollar(100)
82
- # n.cents #=> 100
83
- # n.currency #=> #<Money::Currency id: cad>
84
- def self.ca_dollar(cents)
85
- Money.new(cents, "CAD")
86
- end
87
-
88
- # Creates a new Money object of the given value, using the American dollar
89
- # currency.
90
- #
91
- # @param [Integer] cents The cents value.
92
- #
93
- # @return [Money]
94
- #
95
- # @example
96
- # n = Money.us_dollar(100)
97
- # n.cents #=> 100
98
- # n.currency #=> #<Money::Currency id: usd>
99
- def self.us_dollar(cents)
100
- Money.new(cents, "USD")
101
- end
102
-
103
- # Creates a new Money object of the given value, using the Euro currency.
104
- #
105
- # @param [Integer] cents The cents value.
106
- #
107
- # @return [Money]
108
- #
109
- # @example
110
- # n = Money.euro(100)
111
- # n.cents #=> 100
112
- # n.currency #=> #<Money::Currency id: eur>
113
- def self.euro(cents)
114
- Money.new(cents, "EUR")
115
- end
116
-
117
-
118
- # Creates a new Money object of +amount+ value in dollars,
119
- # with given +currency+.
120
- #
121
- # The amount value is expressed in +dollars+
122
- # where the +dollar+ is the main monetary unit,
123
- # opposite to the subunit-based representation
124
- # used internally by this library called +cents+.
125
- #
126
- # @param [Numeric] amount The money amount, in dollars.
127
- # @param [Currency, String, Symbol] currency The currency format.
128
- # @param [Money::Bank::*] bank The exchange bank to use.
129
- #
130
- # @return [Money]
131
- #
132
- # @example
133
- # Money.new_with_dollars(100)
134
- # #=> #<Money @cents=10000 @currency="USD">
135
- # Money.new_with_dollars(100, "USD")
136
- # #=> #<Money @cents=10000 @currency="USD">
137
- # Money.new_with_dollars(100, "EUR")
138
- # #=> #<Money @cents=10000 @currency="EUR">
139
- #
140
- # @see Money.new
141
- #
142
- def self.new_with_dollars(amount, currency = Money.default_currency, bank = Money.default_bank)
143
- money = from_numeric(amount, currency)
144
- # Hack! You can't change a bank
145
- money.instance_variable_set("@bank", bank)
146
- money
147
- end
148
- # Adds a new exchange rate to the default bank and return the rate.
149
- #
150
- # @param [Currency, String, Symbol] from_currency Currency to exchange from.
151
- # @param [Currency, String, Symbol] to_currency Currency to exchange to.
152
- # @param [Numeric] rate Rate to exchange with.
153
- #
154
- # @return [Numeric]
155
- #
156
- # @example
157
- # Money.add_rate("USD", "CAD", 1.25) #=> 1.25
158
- def self.add_rate(from_currency, to_currency, rate)
159
- Money.default_bank.add_rate(from_currency, to_currency, rate)
160
- end
161
-
162
-
163
- # Creates a new Money object of +cents+ value in cents,
164
- # with given +currency+.
165
- #
166
- # Alternatively you can use the convenience
167
- # methods like {Money.ca_dollar} and {Money.us_dollar}.
168
- #
169
- # @param [Integer] cents The money amount, in cents.
170
- # @param [Currency, String, Symbol] currency The currency format.
171
- # @param [Money::Bank::*] bank The exchange bank to use.
172
- #
173
- # @return [Money]
174
- #
175
- # @example
176
- # Money.new(100)
177
- # #=> #<Money @cents=100 @currency="USD">
178
- # Money.new(100, "USD")
179
- # #=> #<Money @cents=100 @currency="USD">
180
- # Money.new(100, "EUR")
181
- # #=> #<Money @cents=100 @currency="EUR">
182
- #
183
- # @see Money.new_with_dollars
184
- #
185
- def initialize(cents, currency = Money.default_currency, bank = Money.default_bank)
186
- @cents = cents.round.to_i
187
- @currency = Currency.wrap(currency)
188
- @bank = bank
189
- end
190
-
191
- # Returns the value of the money in dollars,
192
- # instead of in cents.
193
- #
194
- # @return [Float]
195
- #
196
- # @example
197
- # Money.new(100).dollars # => 1.0
198
- # Money.new_with_dollars(1).dollar # => 1.0
199
- #
200
- # @see #to_f
201
- # @see #cents
202
- #
203
- def dollars
204
- to_f
205
- end
206
-
207
- # Return string representation of currency object
208
- #
209
- # @return [String]
210
- #
211
- # @example
212
- # Money.new(100, :USD).currency_as_string #=> "USD"
213
- def currency_as_string
214
- self.currency.to_s
215
- end
216
-
217
- # Set currency object using a string
218
- #
219
- # @param [String] val The currency string.
220
- #
221
- # @return [Money::Currency]
222
- #
223
- # @example
224
- # Money.new(100).currency_as_string("CAD") #=> #<Money::Currency id: cad>
225
- def currency_as_string=(val)
226
- @currency = Currency.wrap(val)
227
- end
228
-
229
- # Returns a Fixnum hash value based on the +cents+ and +currency+ attributes
230
- # in order to use functions like & (intersection), group_by, etc.
231
- #
232
- # @return [Fixnum]
233
- #
234
- # @example
235
- # Money.new(100).hash #=> 908351
236
- def hash
237
- [cents.hash, currency.hash].hash
238
- end
239
-
240
- # Uses +Currency#symbol+. If +nil+ is returned, defaults to "¤".
241
- #
242
- # @return [String]
243
- #
244
- # @example
245
- # Money.new(100, "USD").symbol #=> "$"
246
- def symbol
247
- currency.symbol || "¤"
248
- end
249
-
250
- # Returns the amount of money as a string.
251
- #
252
- # @return [String]
253
- #
254
- # @example
255
- # Money.ca_dollar(100).to_s #=> "1.00"
256
- def to_s
257
- unit, subunit = cents.abs.divmod(currency.subunit_to_unit).map{|o| o.to_s}
258
- if currency.decimal_places == 0
259
- return "-#{unit}" if cents < 0
260
- return unit
261
- end
262
- subunit = (("0" * currency.decimal_places) + subunit)[(-1*currency.decimal_places)..-1]
263
- return "-#{unit}#{decimal_mark}#{subunit}" if cents < 0
264
- "#{unit}#{decimal_mark}#{subunit}"
265
- end
266
-
267
- # Return the amount of money as a BigDecimal.
268
- #
269
- # @return [BigDecimal]
270
- #
271
- # @example
272
- # Money.us_dollar(100).to_d => BigDecimal.new("1.0")
273
- def to_d
274
- BigDecimal.new(cents.to_s) / BigDecimal.new(currency.subunit_to_unit.to_s)
275
- end
276
-
277
- # Return the amount of money as a float. Floating points cannot guarantee
278
- # precision. Therefore, this function should only be used when you no longer
279
- # need to represent currency or working with another system that requires
280
- # decimals.
281
- #
282
- # @return [Float]
283
- #
284
- # @example
285
- # Money.us_dollar(100).to_f => 1.0
286
- def to_f
287
- to_d.to_f
288
- end
289
-
290
- # Receive the amount of this money object in another Currency.
291
- #
292
- # @param [Currency, String, Symbol] other_currency Currency to exchange to.
293
- #
294
- # @return [Money]
295
- #
296
- # @example
297
- # Money.new(2000, "USD").exchange_to("EUR")
298
- # Money.new(2000, "USD").exchange_to(Currency.new("EUR"))
299
- def exchange_to(other_currency)
300
- other_currency = Currency.wrap(other_currency)
301
- @bank.exchange_with(self, other_currency)
302
- end
303
-
304
- # Receive a money object with the same amount as the current Money object
305
- # in american dollars.
306
- #
307
- # @return [Money]
308
- #
309
- # @example
310
- # n = Money.new(100, "CAD").as_us_dollar
311
- # n.currency #=> #<Money::Currency id: usd>
312
- def as_us_dollar
313
- exchange_to("USD")
314
- end
315
-
316
- # Receive a money object with the same amount as the current Money object
317
- # in canadian dollar.
318
- #
319
- # @return [Money]
320
- #
321
- # @example
322
- # n = Money.new(100, "USD").as_ca_dollar
323
- # n.currency #=> #<Money::Currency id: cad>
324
- def as_ca_dollar
325
- exchange_to("CAD")
326
- end
327
-
328
- # Receive a money object with the same amount as the current Money object
329
- # in euro.
330
- #
331
- # @return [Money]
332
- #
333
- # @example
334
- # n = Money.new(100, "USD").as_euro
335
- # n.currency #=> #<Money::Currency id: eur>
336
- def as_euro
337
- exchange_to("EUR")
338
- end
339
-
340
- # Conversation to +self+.
341
- #
342
- # @return [self]
343
- def to_money
344
- self
345
- end
346
-
347
- # Common inspect function
348
- #
349
- # @return [String]
350
- def inspect
351
- "#<Money cents:#{cents} currency:#{currency}>"
352
- end
353
-
354
- # Allocates money between different parties without loosing pennies.
355
- # After the mathmatically split has been performed, left over pennies will
356
- # be distributed round-robin amongst the parties. This means that parties
357
- # listed first will likely recieve more pennies then ones that are listed later
358
- #
359
- # @param [0.50, 0.25, 0.25] to give 50% of the cash to party1, 25% ot party2, and 25% to party3.
360
- #
361
- # @return [Array<Money, Money, Money>]
362
- #
363
- # @example
364
- # Money.new(5, "USD").allocate([0.3,0.7)) #=> [Money.new(2), Money.new(3)]
365
- # Money.new(100, "USD").allocate([0.33,0.33,0.33]) #=> [Money.new(34), Money.new(33), Money.new(33)]
366
- def allocate(splits)
367
- allocations = splits.inject(0.0) {|sum, i| sum += i }
368
- raise ArgumentError, "splits add to more then 100%" if (allocations - 1.0) > Float::EPSILON
369
-
370
- left_over = cents
371
-
372
- amounts = splits.collect do |ratio|
373
- fraction = (cents * ratio / allocations).floor
374
- left_over -= fraction
375
- fraction
376
- end
377
-
378
- left_over.times { |i| amounts[i % amounts.length] += 1 }
379
-
380
- return amounts.collect { |cents| Money.new(cents, currency) }
381
- end
382
-
383
- # Split money amongst parties evenly without loosing pennies.
384
- #
385
- # @param [2] number of parties.
386
- #
387
- # @return [Array<Money, Money, Money>]
388
- #
389
- # @example
390
- # Money.new(100, "USD").split(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
391
- def split(num)
392
- raise ArgumentError, "need at least one party" if num < 1
393
- low = Money.new(cents / num)
394
- high = Money.new(low.cents + 1)
395
-
396
- remainder = cents % num
397
- result = []
398
-
399
- num.times do |index|
400
- result[index] = index < remainder ? high : low
401
- end
402
-
403
- return result
404
- end
405
- end
1
+ # encoding: utf-8
2
+ require 'money/bank/variable_exchange'
3
+ require 'money/money/arithmetic'
4
+ require 'money/money/parsing'
5
+ require 'money/money/formatting'
6
+
7
+ # Represents an amount of money in a given currency.
8
+ class Money
9
+ include Comparable
10
+ include Arithmetic
11
+ include Formatting
12
+ include Parsing
13
+
14
+ # The value of the money in cents.
15
+ #
16
+ # @return [Integer]
17
+ attr_reader :cents
18
+
19
+ # The currency the money is in.
20
+ #
21
+ # @return [Currency]
22
+ attr_reader :currency
23
+
24
+ # The +Money::Bank+ based object used to perform currency exchanges with.
25
+ #
26
+ # @return [Money::Bank::*]
27
+ attr_reader :bank
28
+
29
+ # Class Methods
30
+ class << self
31
+ # Each Money object is associated to a bank object, which is responsible
32
+ # for currency exchange. This property allows you to specify the default
33
+ # bank object. The default value for this property is an instance if
34
+ # +Bank::VariableExchange.+ It allows one to specify custom exchange rates.
35
+ #
36
+ # @return [Money::Bank::*]
37
+ attr_accessor :default_bank
38
+
39
+ # The default currency, which is used when +Money.new+ is called without an
40
+ # explicit currency argument. The default value is Currency.new("USD"). The
41
+ # value must be a valid +Money::Currency+ instance.
42
+ #
43
+ # @return [Money::Currency]
44
+ attr_accessor :default_currency
45
+
46
+ # Use this to disable i18n even if it's used by other objects in your app.
47
+ #
48
+ # @return [true,false]
49
+ attr_accessor :use_i18n
50
+ end
51
+
52
+ # Set the default bank for creating new +Money+ objects.
53
+ self.default_bank = Bank::VariableExchange.instance
54
+
55
+ # Set the default currency for creating new +Money+ object.
56
+ self.default_currency = Currency.new("USD")
57
+
58
+ # Default to using i18n
59
+ self.use_i18n = true
60
+
61
+ # Create a new money object with value 0.
62
+ #
63
+ # @param [Currency, String, Symbol] currency The currency to use.
64
+ #
65
+ # @return [Money]
66
+ #
67
+ # @example
68
+ # Money.empty #=> #<Money @cents=0>
69
+ def self.empty(currency = default_currency)
70
+ Money.new(0, currency)
71
+ end
72
+
73
+ # Creates a new Money object of the given value, using the Canadian
74
+ # dollar currency.
75
+ #
76
+ # @param [Integer] cents The cents value.
77
+ #
78
+ # @return [Money]
79
+ #
80
+ # @example
81
+ # n = Money.ca_dollar(100)
82
+ # n.cents #=> 100
83
+ # n.currency #=> #<Money::Currency id: cad>
84
+ def self.ca_dollar(cents)
85
+ Money.new(cents, "CAD")
86
+ end
87
+
88
+ # Creates a new Money object of the given value, using the American dollar
89
+ # currency.
90
+ #
91
+ # @param [Integer] cents The cents value.
92
+ #
93
+ # @return [Money]
94
+ #
95
+ # @example
96
+ # n = Money.us_dollar(100)
97
+ # n.cents #=> 100
98
+ # n.currency #=> #<Money::Currency id: usd>
99
+ def self.us_dollar(cents)
100
+ Money.new(cents, "USD")
101
+ end
102
+
103
+ # Creates a new Money object of the given value, using the Euro currency.
104
+ #
105
+ # @param [Integer] cents The cents value.
106
+ #
107
+ # @return [Money]
108
+ #
109
+ # @example
110
+ # n = Money.euro(100)
111
+ # n.cents #=> 100
112
+ # n.currency #=> #<Money::Currency id: eur>
113
+ def self.euro(cents)
114
+ Money.new(cents, "EUR")
115
+ end
116
+
117
+
118
+ # Creates a new Money object of +amount+ value in dollars,
119
+ # with given +currency+.
120
+ #
121
+ # The amount value is expressed in +dollars+
122
+ # where the +dollar+ is the main monetary unit,
123
+ # opposite to the subunit-based representation
124
+ # used internally by this library called +cents+.
125
+ #
126
+ # @param [Numeric] amount The money amount, in dollars.
127
+ # @param [Currency, String, Symbol] currency The currency format.
128
+ # @param [Money::Bank::*] bank The exchange bank to use.
129
+ #
130
+ # @return [Money]
131
+ #
132
+ # @example
133
+ # Money.new_with_dollars(100)
134
+ # #=> #<Money @cents=10000 @currency="USD">
135
+ # Money.new_with_dollars(100, "USD")
136
+ # #=> #<Money @cents=10000 @currency="USD">
137
+ # Money.new_with_dollars(100, "EUR")
138
+ # #=> #<Money @cents=10000 @currency="EUR">
139
+ #
140
+ # @see Money.new
141
+ #
142
+ def self.new_with_dollars(amount, currency = Money.default_currency, bank = Money.default_bank)
143
+ money = from_numeric(amount, currency)
144
+ # Hack! You can't change a bank
145
+ money.instance_variable_set("@bank", bank)
146
+ money
147
+ end
148
+ # Adds a new exchange rate to the default bank and return the rate.
149
+ #
150
+ # @param [Currency, String, Symbol] from_currency Currency to exchange from.
151
+ # @param [Currency, String, Symbol] to_currency Currency to exchange to.
152
+ # @param [Numeric] rate Rate to exchange with.
153
+ #
154
+ # @return [Numeric]
155
+ #
156
+ # @example
157
+ # Money.add_rate("USD", "CAD", 1.25) #=> 1.25
158
+ def self.add_rate(from_currency, to_currency, rate)
159
+ Money.default_bank.add_rate(from_currency, to_currency, rate)
160
+ end
161
+
162
+
163
+ # Creates a new Money object of +cents+ value in cents,
164
+ # with given +currency+.
165
+ #
166
+ # Alternatively you can use the convenience
167
+ # methods like {Money.ca_dollar} and {Money.us_dollar}.
168
+ #
169
+ # @param [Integer] cents The money amount, in cents.
170
+ # @param [Currency, String, Symbol] currency The currency format.
171
+ # @param [Money::Bank::*] bank The exchange bank to use.
172
+ #
173
+ # @return [Money]
174
+ #
175
+ # @example
176
+ # Money.new(100)
177
+ # #=> #<Money @cents=100 @currency="USD">
178
+ # Money.new(100, "USD")
179
+ # #=> #<Money @cents=100 @currency="USD">
180
+ # Money.new(100, "EUR")
181
+ # #=> #<Money @cents=100 @currency="EUR">
182
+ #
183
+ # @see Money.new_with_dollars
184
+ #
185
+ def initialize(cents, currency = Money.default_currency, bank = Money.default_bank)
186
+ @cents = cents.round.to_i
187
+ @currency = Currency.wrap(currency)
188
+ @bank = bank
189
+ end
190
+
191
+ # Returns the value of the money in dollars,
192
+ # instead of in cents.
193
+ #
194
+ # @return [Float]
195
+ #
196
+ # @example
197
+ # Money.new(100).dollars # => 1.0
198
+ # Money.new_with_dollars(1).dollar # => 1.0
199
+ #
200
+ # @see #to_f
201
+ # @see #cents
202
+ #
203
+ def dollars
204
+ to_f
205
+ end
206
+
207
+ # Return string representation of currency object
208
+ #
209
+ # @return [String]
210
+ #
211
+ # @example
212
+ # Money.new(100, :USD).currency_as_string #=> "USD"
213
+ def currency_as_string
214
+ self.currency.to_s
215
+ end
216
+
217
+ # Set currency object using a string
218
+ #
219
+ # @param [String] val The currency string.
220
+ #
221
+ # @return [Money::Currency]
222
+ #
223
+ # @example
224
+ # Money.new(100).currency_as_string("CAD") #=> #<Money::Currency id: cad>
225
+ def currency_as_string=(val)
226
+ @currency = Currency.wrap(val)
227
+ end
228
+
229
+ # Returns a Fixnum hash value based on the +cents+ and +currency+ attributes
230
+ # in order to use functions like & (intersection), group_by, etc.
231
+ #
232
+ # @return [Fixnum]
233
+ #
234
+ # @example
235
+ # Money.new(100).hash #=> 908351
236
+ def hash
237
+ [cents.hash, currency.hash].hash
238
+ end
239
+
240
+ # Uses +Currency#symbol+. If +nil+ is returned, defaults to "¤".
241
+ #
242
+ # @return [String]
243
+ #
244
+ # @example
245
+ # Money.new(100, "USD").symbol #=> "$"
246
+ def symbol
247
+ currency.symbol || "¤"
248
+ end
249
+
250
+ # Returns the amount of money as a string.
251
+ #
252
+ # @return [String]
253
+ #
254
+ # @example
255
+ # Money.ca_dollar(100).to_s #=> "1.00"
256
+ def to_s
257
+ unit, subunit = cents.abs.divmod(currency.subunit_to_unit).map{|o| o.to_s}
258
+ if currency.decimal_places == 0
259
+ return "-#{unit}" if cents < 0
260
+ return unit
261
+ end
262
+ subunit = (("0" * currency.decimal_places) + subunit)[(-1*currency.decimal_places)..-1]
263
+ return "-#{unit}#{decimal_mark}#{subunit}" if cents < 0
264
+ "#{unit}#{decimal_mark}#{subunit}"
265
+ end
266
+
267
+ # Return the amount of money as a BigDecimal.
268
+ #
269
+ # @return [BigDecimal]
270
+ #
271
+ # @example
272
+ # Money.us_dollar(100).to_d => BigDecimal.new("1.0")
273
+ def to_d
274
+ BigDecimal.new(cents.to_s) / BigDecimal.new(currency.subunit_to_unit.to_s)
275
+ end
276
+
277
+ # Return the amount of money as a float. Floating points cannot guarantee
278
+ # precision. Therefore, this function should only be used when you no longer
279
+ # need to represent currency or working with another system that requires
280
+ # decimals.
281
+ #
282
+ # @return [Float]
283
+ #
284
+ # @example
285
+ # Money.us_dollar(100).to_f => 1.0
286
+ def to_f
287
+ to_d.to_f
288
+ end
289
+
290
+ # Receive the amount of this money object in another Currency.
291
+ #
292
+ # @param [Currency, String, Symbol] other_currency Currency to exchange to.
293
+ #
294
+ # @return [Money]
295
+ #
296
+ # @example
297
+ # Money.new(2000, "USD").exchange_to("EUR")
298
+ # Money.new(2000, "USD").exchange_to(Currency.new("EUR"))
299
+ def exchange_to(other_currency)
300
+ other_currency = Currency.wrap(other_currency)
301
+ @bank.exchange_with(self, other_currency)
302
+ end
303
+
304
+ # Receive a money object with the same amount as the current Money object
305
+ # in american dollars.
306
+ #
307
+ # @return [Money]
308
+ #
309
+ # @example
310
+ # n = Money.new(100, "CAD").as_us_dollar
311
+ # n.currency #=> #<Money::Currency id: usd>
312
+ def as_us_dollar
313
+ exchange_to("USD")
314
+ end
315
+
316
+ # Receive a money object with the same amount as the current Money object
317
+ # in canadian dollar.
318
+ #
319
+ # @return [Money]
320
+ #
321
+ # @example
322
+ # n = Money.new(100, "USD").as_ca_dollar
323
+ # n.currency #=> #<Money::Currency id: cad>
324
+ def as_ca_dollar
325
+ exchange_to("CAD")
326
+ end
327
+
328
+ # Receive a money object with the same amount as the current Money object
329
+ # in euro.
330
+ #
331
+ # @return [Money]
332
+ #
333
+ # @example
334
+ # n = Money.new(100, "USD").as_euro
335
+ # n.currency #=> #<Money::Currency id: eur>
336
+ def as_euro
337
+ exchange_to("EUR")
338
+ end
339
+
340
+ # Conversation to +self+.
341
+ #
342
+ # @return [self]
343
+ def to_money
344
+ self
345
+ end
346
+
347
+ # Common inspect function
348
+ #
349
+ # @return [String]
350
+ def inspect
351
+ "#<Money cents:#{cents} currency:#{currency}>"
352
+ end
353
+
354
+ # Allocates money between different parties without loosing pennies.
355
+ # After the mathmatically split has been performed, left over pennies will
356
+ # be distributed round-robin amongst the parties. This means that parties
357
+ # listed first will likely recieve more pennies then ones that are listed later
358
+ #
359
+ # @param [0.50, 0.25, 0.25] to give 50% of the cash to party1, 25% ot party2, and 25% to party3.
360
+ #
361
+ # @return [Array<Money, Money, Money>]
362
+ #
363
+ # @example
364
+ # Money.new(5, "USD").allocate([0.3,0.7)) #=> [Money.new(2), Money.new(3)]
365
+ # Money.new(100, "USD").allocate([0.33,0.33,0.33]) #=> [Money.new(34), Money.new(33), Money.new(33)]
366
+ def allocate(splits)
367
+ allocations = splits.inject(0.0) {|sum, i| sum += i }
368
+ raise ArgumentError, "splits add to more then 100%" if (allocations - 1.0) > Float::EPSILON
369
+
370
+ left_over = cents
371
+
372
+ amounts = splits.collect do |ratio|
373
+ fraction = (cents * ratio / allocations).floor
374
+ left_over -= fraction
375
+ fraction
376
+ end
377
+
378
+ left_over.times { |i| amounts[i % amounts.length] += 1 }
379
+
380
+ return amounts.collect { |cents| Money.new(cents, currency) }
381
+ end
382
+
383
+ # Split money amongst parties evenly without loosing pennies.
384
+ #
385
+ # @param [2] number of parties.
386
+ #
387
+ # @return [Array<Money, Money, Money>]
388
+ #
389
+ # @example
390
+ # Money.new(100, "USD").split(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
391
+ def split(num)
392
+ raise ArgumentError, "need at least one party" if num < 1
393
+ low = Money.new(cents / num)
394
+ high = Money.new(low.cents + 1)
395
+
396
+ remainder = cents % num
397
+ result = []
398
+
399
+ num.times do |index|
400
+ result[index] = index < remainder ? high : low
401
+ end
402
+
403
+ return result
404
+ end
405
+ end