simple_money 0.1.0
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.
- data/CHANGELOG.md +3 -0
- data/LICENSE +20 -0
- data/README.md +21 -0
- data/lib/simple_money/money.rb +287 -0
- data/lib/simple_money.rb +1 -0
- metadata +99 -0
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Shane Emmons
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
SimpleMoney
|
2
|
+
===========
|
3
|
+
|
4
|
+
This gem is intended for working with financial calculations where you need
|
5
|
+
highly accurate results. When performing calculations where fractional cents
|
6
|
+
are introduced, these fractional cents are stored in an overflow bucket for the
|
7
|
+
user to examine as needed.
|
8
|
+
|
9
|
+
Usage:
|
10
|
+
require 'simple_money'
|
11
|
+
|
12
|
+
a = Money.new(1_00, :as => :cents)
|
13
|
+
a.cents #=> 100
|
14
|
+
b = a * 1.555
|
15
|
+
b.cents #=> 156
|
16
|
+
Money.overflow #=> #<BigDecimal:... '-0.5E0',4(16)>
|
17
|
+
|
18
|
+
Copyright
|
19
|
+
---------
|
20
|
+
|
21
|
+
Copyright (c) 2011 Shane Emmons. See {file:LICENSE} for details.
|
@@ -0,0 +1,287 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
|
3
|
+
##
|
4
|
+
# Used to work with financial calculations. Tries to avoid the pitfalls of
|
5
|
+
# using Float by storing the value in cents. All calculations are floored.
|
6
|
+
class Money
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
##
|
11
|
+
# The valid values for :as
|
12
|
+
VALID_AS_VALUES = [:cents, :decimal]
|
13
|
+
|
14
|
+
##
|
15
|
+
# The valid values for :rounding_method
|
16
|
+
VALID_ROUNDING_METHOD_VALUES = [:away_from_zero, :toward_zero,
|
17
|
+
:nearest_up, :nearest_down, :bankers, :up, :down]
|
18
|
+
|
19
|
+
##
|
20
|
+
# Translations from SimpleMoney rounding methods to BigDecimal rounding
|
21
|
+
# method.
|
22
|
+
ROUNDING_METHOD_TRANSLATION = {
|
23
|
+
:away_from_zero => BigDecimal::ROUND_UP,
|
24
|
+
:toward_zero => BigDecimal::ROUND_DOWN,
|
25
|
+
:nearest_up => BigDecimal::ROUND_HALF_UP,
|
26
|
+
:nearest_down => BigDecimal::ROUND_HALF_DOWN,
|
27
|
+
:bankers => BigDecimal::ROUND_HALF_EVEN,
|
28
|
+
:up => BigDecimal::ROUND_CEILING,
|
29
|
+
:down => BigDecimal::ROUND_FLOOR,
|
30
|
+
}
|
31
|
+
|
32
|
+
##
|
33
|
+
# @return [Symbol] The default :as used to create a new Money (defaults to
|
34
|
+
# :cents).
|
35
|
+
attr_reader :default_as
|
36
|
+
|
37
|
+
##
|
38
|
+
# Set the default :as used to create a new Money.
|
39
|
+
#
|
40
|
+
# @param [Symbol] as The default to use.
|
41
|
+
#
|
42
|
+
# @return [Symbol]
|
43
|
+
#
|
44
|
+
# @raise [ArgumentError] Will raise an ArgumentError unless as is valid.
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# Money.default_as = :cents #=> :cents
|
48
|
+
# Money.default_as = :decimal #=> :decimal
|
49
|
+
def default_as=(as)
|
50
|
+
raise ArgumentError, "invalid `as`" unless (
|
51
|
+
valid_as? as
|
52
|
+
)
|
53
|
+
@default_as = as
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Returns true if argument is a valid value for :as, otherwise false.
|
58
|
+
#
|
59
|
+
# @param [Symbol] as The value to check.
|
60
|
+
#
|
61
|
+
# @return [true,false]
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# Money.valid_as? :cents #=> True
|
65
|
+
# Money.valid_as? :foo #=> False
|
66
|
+
def valid_as?(as)
|
67
|
+
VALID_AS_VALUES.include? as
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# @return [Symbol] The default :rounding_method used when calculations do
|
72
|
+
# not result in an Integer (defaults to :bankers).
|
73
|
+
attr_reader :default_rounding_method
|
74
|
+
|
75
|
+
##
|
76
|
+
# Set the default :rounding_method used when calculations do not result in
|
77
|
+
# and Integer.
|
78
|
+
#
|
79
|
+
# @param [Symbol] rounding_method The default to use.
|
80
|
+
#
|
81
|
+
# @return [Symbol]
|
82
|
+
#
|
83
|
+
# @raise [ArgumentError] Will raise an ArgumentError unless rounding_method
|
84
|
+
# is valid.
|
85
|
+
#
|
86
|
+
# @example
|
87
|
+
# Money.default_rounding_method = :up #=> :up
|
88
|
+
# Money.default_rounding_method = :down #=> :down
|
89
|
+
def default_rounding_method=(rounding_method)
|
90
|
+
raise ArgumentError, "invalid `rounding_method`" unless (
|
91
|
+
valid_rounding_method? rounding_method
|
92
|
+
)
|
93
|
+
@default_rounding_method = rounding_method
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Returns true if argument is a valid value for :rounding_method, otherwise
|
98
|
+
# false.
|
99
|
+
#
|
100
|
+
# @param [Symbol] rounding_method The value to check.
|
101
|
+
#
|
102
|
+
# @return [true,false]
|
103
|
+
#
|
104
|
+
# @example
|
105
|
+
# Money.valid_rounding_method? :up #=> True
|
106
|
+
# Money.valid_rounding_method? :foo #=> False
|
107
|
+
def valid_rounding_method?(rounding_method)
|
108
|
+
VALID_ROUNDING_METHOD_VALUES.include? rounding_method
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# @return [BigDecimal] The factional cents left over from any transactions
|
113
|
+
# that were rounded.
|
114
|
+
attr_reader :overflow
|
115
|
+
|
116
|
+
##
|
117
|
+
# Resets the overflow bucket to 0.
|
118
|
+
#
|
119
|
+
# @return [BigDecimal]
|
120
|
+
def reset_overflow
|
121
|
+
@overflow = BigDecimal("0")
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# Returns n rounded to an integer using the given rounding method, or the
|
126
|
+
# default rounding method when none is provided. When rounding, the
|
127
|
+
# fractional cents are added to Money.overflow.
|
128
|
+
#
|
129
|
+
# @param [#to_s] n The value to round.
|
130
|
+
# @param [Symbol] rounding_method The rounding method to use.
|
131
|
+
#
|
132
|
+
# @return [Fixnum]
|
133
|
+
#
|
134
|
+
# @raise [ArgumentError] Will raise an argument error if an invalid
|
135
|
+
# rounding method is given.
|
136
|
+
#
|
137
|
+
# @example
|
138
|
+
# Money.round(1.5, :bankers) #=> 2
|
139
|
+
def round(n, rounding_method = default_rounding_method)
|
140
|
+
raise ArgumentError, "invalid `rounding_method`" unless (
|
141
|
+
valid_rounding_method? rounding_method
|
142
|
+
)
|
143
|
+
|
144
|
+
original = BigDecimal(n.to_s)
|
145
|
+
rounded = original.round(
|
146
|
+
0,
|
147
|
+
ROUNDING_METHOD_TRANSLATION[rounding_method]
|
148
|
+
)
|
149
|
+
@overflow += original - rounded
|
150
|
+
rounded.to_i
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
@default_as = :cents
|
155
|
+
@default_rounding_method = :bankers
|
156
|
+
@overflow = BigDecimal("0")
|
157
|
+
|
158
|
+
##
|
159
|
+
# @return [Integer] The value of the object in cents.
|
160
|
+
attr_reader :cents
|
161
|
+
|
162
|
+
##
|
163
|
+
# @return [Symbol] The rounding method used when calculations result in
|
164
|
+
# fractions of a cent.
|
165
|
+
attr_reader :rounding_method
|
166
|
+
|
167
|
+
##
|
168
|
+
# Creates a new Money. If :as is set to :cents, n will be coerced to an
|
169
|
+
# Integer. If :as is set to :decimal, n will be coerced to a BigDecimal.
|
170
|
+
#
|
171
|
+
# @param [#to_s] n Value of the new object.
|
172
|
+
# @param [Hash] options options used to build the new object.
|
173
|
+
# @option options [Symbol] :as How n is represented (defaults to
|
174
|
+
# self.class.default_as). Valid values are :cents and :decimal.
|
175
|
+
# @option options [Symbol] :rounding_method How any calculations resulting in
|
176
|
+
# fractions of a cent should be rounded.
|
177
|
+
#
|
178
|
+
# @raise [ArgumentError] Will raise an ArgumentError if :as is not valid.
|
179
|
+
#
|
180
|
+
# @example
|
181
|
+
# Money.new #=> #<Money:... @cents: 0>
|
182
|
+
# Money.new(1) #=> #<Money:... @cents: 1>
|
183
|
+
# Money.new(1_00) #=> #<Money:... @cents: 100>
|
184
|
+
# Money.new(1_00, :as => :cents) #=> #<Money:... @cents: 100>
|
185
|
+
# Money.new(1_00, :as => :decimal) #=> #<Money:... @cents: 10000>
|
186
|
+
# Money.new(1.99, :as => :cents) #=> #<Money:... @cents: 1>
|
187
|
+
# Money.new(1.99, :as => :decimal) #=> #<Money:... @cents: 199>
|
188
|
+
def initialize(n = 0, options = {})
|
189
|
+
options = {
|
190
|
+
:rounding_method => self.class.default_rounding_method,
|
191
|
+
:as => self.class.default_as
|
192
|
+
}.merge(options)
|
193
|
+
|
194
|
+
raise ArgumentError, "invalid `rounding_method`" unless (
|
195
|
+
self.class.valid_rounding_method? options[:rounding_method]
|
196
|
+
)
|
197
|
+
@rounding_method = options[:rounding_method]
|
198
|
+
|
199
|
+
raise ArgumentError, "invalid `as`" unless (
|
200
|
+
self.class.valid_as? options[:as]
|
201
|
+
)
|
202
|
+
|
203
|
+
@cents = case options[:as]
|
204
|
+
when :cents
|
205
|
+
Money.round(BigDecimal(n.to_s), rounding_method)
|
206
|
+
when :decimal
|
207
|
+
Money.round(BigDecimal(n.to_s) * 100, rounding_method)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
##
|
212
|
+
# Add two Money objects; return the results as a new Money.
|
213
|
+
#
|
214
|
+
# @param [Money] n The object to add.
|
215
|
+
#
|
216
|
+
# @return [Money]
|
217
|
+
#
|
218
|
+
# @raise [ArgumentError] Will raise an ArgumentError unless n is a Money.
|
219
|
+
#
|
220
|
+
# @example
|
221
|
+
# Money.new(1) + Money.new(2) #=> #<Money:... @cents: 3>
|
222
|
+
def +(n)
|
223
|
+
raise ArgumentError, "n must be a Money" unless n.is_a? Money
|
224
|
+
Money.new(self.cents + n.cents, :as => :cents)
|
225
|
+
end
|
226
|
+
|
227
|
+
##
|
228
|
+
# Subtract two Money; return the results as a new Money.
|
229
|
+
#
|
230
|
+
# @param [Money] n The object to subtract.
|
231
|
+
#
|
232
|
+
# @return [Money]
|
233
|
+
#
|
234
|
+
# @raise [ArgumentError] Will raise an ArgumentError unless n is a Money.
|
235
|
+
#
|
236
|
+
# @example
|
237
|
+
# Money.new(2) - Money.new(1) #=> #<Money:... @cents: 1>
|
238
|
+
def -(n)
|
239
|
+
raise ArgumentError, "n must be a Money" unless n.is_a? Money
|
240
|
+
Money.new(self.cents - n.cents, :as => :cents)
|
241
|
+
end
|
242
|
+
|
243
|
+
##
|
244
|
+
# Multiply Money by a Numeric; return the results as a new Money.
|
245
|
+
#
|
246
|
+
# @param [Numeric] n The object to multiply. n will be coerced to a
|
247
|
+
# BigDecimal before any calculations are done.
|
248
|
+
#
|
249
|
+
# @return [Money]
|
250
|
+
#
|
251
|
+
# @raise [ArgumentError] Will raise an ArgumentError unless n is a Numeric.
|
252
|
+
#
|
253
|
+
# @example
|
254
|
+
# Money.new(2) * 2 #=> #<Money:... @cents: 4>
|
255
|
+
def *(n)
|
256
|
+
raise ArgumentError, "n must be a Numeric" unless n.is_a? Numeric
|
257
|
+
|
258
|
+
Money.new(self.cents * BigDecimal(n.to_s), :as => :cents)
|
259
|
+
end
|
260
|
+
|
261
|
+
##
|
262
|
+
# Divide Money by a Money/Numeric; return the results as a new
|
263
|
+
# Numeric/Money.
|
264
|
+
#
|
265
|
+
# @param [Money,Numeric] n The object to divide. If n is Numeric, it will be
|
266
|
+
# coerced to a BigDecimal before any calculations are done.
|
267
|
+
#
|
268
|
+
# @return [Numeric,Money]
|
269
|
+
#
|
270
|
+
# @raise [ArgumentError] Will raise an ArgumentError unless n is a Money or
|
271
|
+
# Numeric.
|
272
|
+
#
|
273
|
+
# @example
|
274
|
+
# Money.new(10) / Money.new(5) #=> 2
|
275
|
+
# Money.new(10) / #=> #<Money:... @cents: 2>
|
276
|
+
def /(n)
|
277
|
+
case n
|
278
|
+
when Money
|
279
|
+
BigDecimal(self.cents.to_s) / BigDecimal(n.cents.to_s)
|
280
|
+
when Numeric
|
281
|
+
Money.new(self.cents / BigDecimal(n.to_s), :as => :cents)
|
282
|
+
else
|
283
|
+
raise ArgumentError, "n must be a Money or Numeric"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
data/lib/simple_money.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'simple_money/money'
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple_money
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Shane Emmons
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-01-06 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 2
|
30
|
+
- 0
|
31
|
+
- 0
|
32
|
+
version: 2.0.0
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: yard
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
description: This gem is intended for working with financial calculations where you need highly accurate results.
|
49
|
+
email: semmons99@gmail.com
|
50
|
+
executables: []
|
51
|
+
|
52
|
+
extensions: []
|
53
|
+
|
54
|
+
extra_rdoc_files: []
|
55
|
+
|
56
|
+
files:
|
57
|
+
- lib/simple_money/money.rb
|
58
|
+
- lib/simple_money.rb
|
59
|
+
- CHANGELOG.md
|
60
|
+
- LICENSE
|
61
|
+
- README.md
|
62
|
+
has_rdoc: true
|
63
|
+
homepage: http://github.com/semmons99/simple_money
|
64
|
+
licenses: []
|
65
|
+
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
segments:
|
77
|
+
- 1
|
78
|
+
- 8
|
79
|
+
- 7
|
80
|
+
version: 1.8.7
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
segments:
|
87
|
+
- 1
|
88
|
+
- 3
|
89
|
+
- 7
|
90
|
+
version: 1.3.7
|
91
|
+
requirements: []
|
92
|
+
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 1.3.7
|
95
|
+
signing_key:
|
96
|
+
specification_version: 3
|
97
|
+
summary: Library to work with money/currency.
|
98
|
+
test_files: []
|
99
|
+
|