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 ADDED
@@ -0,0 +1,3 @@
1
+ SimpleMoney 0.1.0
2
+ ===================
3
+ - Initial release.
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
@@ -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
+