minting 0.3.2 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 197525ac986a8283ad9caf5fbc1c828b3c41c2da5a544b398357dccb8d6de430
4
- data.tar.gz: ceed85f0f3617e83a1c50f43d4fb0a3f558f335af2f91c78c751278ef3cb0e23
3
+ metadata.gz: 4dfd2f98840b009208b9cccab86e9ca5d093300351e9e2031e70e03b93315638
4
+ data.tar.gz: 47a92df548558e20fd7bea3caa10a8966db91341d998e9712405af3d97fe12ae
5
5
  SHA512:
6
- metadata.gz: 958922e3db6e4ee1a53a4e42feed3a1a6fb41e129ca50b7b2335b713e689d0c5172ba7d65f6b4e37f82cb6934f39a4a03f5a8d9bf55e2e5842f68caf894e9c98
7
- data.tar.gz: 593f757a56e95667a80af223a8dde7c55771cbd24529a04625372c78d2266f7a86d0117b45a62bbf005aed6cd5434e33ece7ac898baee4609a73badf4add448e
6
+ metadata.gz: 175607c53124f16cac60c699a6ed6f042b3c3e960055bca1ccb7ba822a05b6cf8401fb66902e148a9ed50b43524ce6e708feaa72cac15f6ddd8b9cf3e9189cc6
7
+ data.tar.gz: 4fc09433abd38014d0d99c16aed5f88eb36ad039e422b0ffa305d3335da1753a23bae2bfcf775ce6142edf844463ed3942710d87f034375f99e04ff1b7e92812
data/README.md CHANGED
@@ -1,61 +1,63 @@
1
1
  # Minting
2
2
 
3
- Yet another Ruby library for dealing with money and currency.
3
+ Fast, precise, and developer-friendly money handling for Ruby.
4
4
 
5
- Work in progress, please wait release 1.0.0
5
+ [![Gem Version](https://badge.fury.io/rb/minting.svg)](https://badge.fury.io/rb/minting)
6
6
 
7
- ## Installation
7
+ ## Why Minting?
8
8
 
9
- Option 1: Via bundler command
9
+ **Tired of floating-point errors in financial calculations?** Minting uses Rational numbers for perfect precision.
10
10
 
11
- ```shell
12
- bundle add minting
13
- bundle install
14
- ```
11
+ **Need performance?** Minting is 2x faster than alternatives.
15
12
 
16
- Option 2: add the line below to your application's Gemfile:
13
+ **Want a clean API?** Minting provides an intuitive interface with helpful error messages.
17
14
 
18
- ```ruby
19
- gem 'minting'
20
- ```
15
+ **Looking for a proven alternative?** Check out the established [Money gem](https://github.com/RubyMoney/money) with thousands of stars on GitHub.
21
16
 
22
- or, if you want latest development version from Github
17
+ **Rails**? Use the [minting-rails](https://github.com/gferraz/minting-rails) companion gem
18
+
19
+ ## Quick start
23
20
 
24
21
  ```ruby
25
- gem 'minting', git: 'https://github.com/gferraz/minting.git'
26
- ```
22
+ require 'minting'
27
23
 
28
- and execute:
24
+ price = Mint.money(19.99, 'USD') #=> [USD 19.99]
25
+ tax = price * 0.08 #=> [USD 1.60]
26
+ total = price + tax #=> [USD 21.59]
29
27
 
30
- ```shell
31
- bundle install
28
+ total.to_s #=> "$21.59"
29
+ total.currency_code #=> "USD"
32
30
  ```
33
31
 
34
- Option3: Install it yourself with:
32
+ ## Features
35
33
 
36
- ```shell
37
- gem install minting
38
- ````
34
+ - Arithmetic: `+ - * /`, unary minus, `abs`
35
+ - Comparisons: `==`, `<=>`, `zero?`, `nonzero?`, `positive?`, `negative?`
36
+ - Formatting: `to_s` with custom formats, delimiters, separators
37
+ - Serialization: `to_json`, `to_i`, `to_f`, `to_r`, `to_d`
38
+ - Allocation utilities: `split(quantity)`, `allocate([ratios])`
39
+ - Numeric Refinements for ergonomics: `10.dollars`, `3.euros`, `4.to_money('USD')`
40
+ - Currency registry with 117+ currencies and custom registration
39
41
 
40
42
  ## Usage
41
43
 
42
44
  ```ruby
43
45
  require 'minting'
44
46
 
45
- # 10.00 USD
46
- ten_dollars = Mint.money(10, 'USD') #=> [USD 10.00]
47
- ten_dollars.to_i #=> 10
48
- ten_dollars.currency_code #=> "USD"
47
+ # Create money
48
+ ten = Mint.money(10, 'USD') #=> [USD 10.00]
49
49
 
50
- # Comparisons
51
- ten_dollars = Mint.money(10, 'USD')
50
+ # Create money using Numeric refinements
51
+ using Mint
52
52
 
53
- ten_dollars == Mint.money(10, 'USD') #=> true
54
- ten_dollars == Mint.money(11, 'USD') #=> false
55
- ten_dollars == Mint.money(10, 'EUR') #=> false
53
+ 1.dollar == Mint.money(1, 'USD') #=> true
54
+ ten = 10.dollars #=> [USD 10.00]
55
+ 4.to_money('USD') #=> [USD 4.00]
56
56
 
57
- ten_dollars.eql? Mint.money(10, 'USD') #=> true
58
- ten_dollars.hash == Mint.money(10, 'USD').hash #=> true
57
+ # Comparisons
58
+ ten == 10.dollars #=> true
59
+ ten == Mint.money(10, 'EUR') #=> false
60
+ ten > Mint.money(9.99, 'USD') #=> true
59
61
 
60
62
  # Format (uses Kernel.format internally)
61
63
  price = Mint.money(9.99, 'USD')
@@ -77,31 +79,97 @@ price_in_euros.to_s(format: '%<symbol>2s%<amount>+10f') #=> " € +12.34"
77
79
 
78
80
  # Json serialization
79
81
 
80
- price.to_json # "{"currency": "USD", "amount": "9.99"}
82
+ price.to_json # => "{ "currency": "USD", "amount": "9.99" }"
81
83
 
82
- # Allocation and split
84
+ # Proportional allocation and split
83
85
 
84
- ten_dollars.split(3) #=> [[USD 3.34], [USD 3.33], [USD 3.33]]
85
- ten_dollars.split(7) #=> [[USD 1.42], [USD 1.43], [USD 1.43], [USD 1.43], [USD 1.43], [USD 1.43], [USD 1.43]]
86
+ ten.split(3) #=> [[USD 3.34], [USD 3.33], [USD 3.33]]
87
+ ten.allocate([1, 2, 3]) #=> [[USD 1.67], [USD 3.33], [USD 5.00]]
86
88
 
87
- ten_dollars.allocate([1, 2, 3]) #=> [[USD 1.67], [USD 3.33], [USD 5.00]]
89
+ # Ranges and enumeration are supported
88
90
 
89
- # Numeric refinements
90
- using Mint
91
+ 1.dollar..10.dollars #=> [USD 1.00]..[USD 10.00]
92
+ (1.dollar..3.dollars).step(1.dollar).to_a #=> [[USD 1.00], [USD 2.00], [USD 3.00]]
91
93
 
92
- 1.dollar == Mint.money(1, 'USD') #=> true
93
- 3.euros == Mint.money(2, 'EUR') #=> true
94
- 4.mint('USD') == 4.dollars #=> true
95
- 4.to_money('USD') == 4.dollars #=> true
94
+ ```
96
95
 
96
+ ## Installation
97
+
98
+ Option 1: Via bundler command
99
+
100
+ ```shell
101
+ bundle add minting
102
+ bundle install
97
103
  ```
98
104
 
99
- ## Release 1.0 Plan
105
+ Option 2: add the line below to your application's Gemfile:
100
106
 
101
- - Localization: I18n
102
- - Arithmetics: div, mod
103
- - Mint.parse
107
+ ```ruby
108
+ gem 'minting'
109
+ ```
110
+
111
+ or, if you want latest development version from Github
112
+
113
+ ```ruby
114
+ gem 'minting', git: 'https://github.com/gferraz/minting.git'
115
+ ```
116
+
117
+ and execute:
118
+
119
+ ```shell
120
+ bundle install
121
+ ```
122
+
123
+ Option 3: Install it yourself with:
124
+
125
+ ```shell
126
+ gem install minting
127
+ ```
128
+
129
+ ## Roadmap
130
+
131
+ - Improve formatting features
132
+ - Localization (I18n-aware formatting)
133
+ - `Mint.parse` for parsing human strings into money
134
+ - Basic exchange-rate conversions
104
135
 
105
136
  ## Contributing
106
137
 
107
138
  Bug reports and pull requests are welcome on GitHub at <https://github.com/gferraz/minting>.
139
+
140
+ 1. Fork and create a feature branch
141
+ 2. Run the test suite: `rake`
142
+ 3. Run performance suites as needed: `BENCH=true rake bench:performance`
143
+ 4. Open a PR with a clear description and benchmarks if relevant
144
+
145
+
146
+ ## Performance
147
+
148
+ This gem includes a performance suite under `test/performance`:
149
+
150
+ - Core operations (creation, arithmetic, comparisons)
151
+ - Algorithm benchmarks (split, allocate)
152
+ - Memory and GC pressure tests
153
+ - Competitive benchmarks vs `money` gem
154
+
155
+ On a typical machine, reference numbers are:
156
+
157
+ - Money creation: ~1.6M ops/sec
158
+ - Addition: ~1.7M ops/sec
159
+
160
+ Run locally:
161
+
162
+ ```bash
163
+ # All performance suites
164
+ BENCH=true rake bench:performance
165
+
166
+ # Competitive vs money gem
167
+ BENCH=true rake bench:competitive
168
+
169
+ # Regression checks
170
+ rake bench:regression
171
+ ```
172
+
173
+ ## License
174
+
175
+ MIT
data/Rakefile CHANGED
@@ -16,6 +16,27 @@ Rake::TestTask.new(:bench) do |t|
16
16
  t.pattern = 'test/**/*_benchmark.rb'
17
17
  end
18
18
 
19
+ # Performance benchmark tasks
20
+ Rake::TestTask.new('bench:performance') do |t|
21
+ t.libs = %w[lib test]
22
+ t.pattern = 'test/performance/*_benchmark.rb'
23
+ t.ruby_opts << '-r test_helper.rb'
24
+ end
25
+
26
+ Rake::TestTask.new('bench:regression') do |t|
27
+ t.libs = %w[lib test]
28
+ t.pattern = 'test/performance/regression_benchmark.rb'
29
+ t.ruby_opts << '-r test_helper.rb'
30
+ end
31
+
32
+ Rake::TestTask.new('bench:competitive') do |t|
33
+ t.libs = %w[lib test]
34
+ t.pattern = 'test/performance/competitive_benchmark.rb'
35
+ t.ruby_opts << '-r test_helper.rb'
36
+ end
37
+
38
+ task 'bench:all' => ['bench', 'bench:performance']
39
+
19
40
  RuboCop::RakeTask.new(:cop)
20
41
 
21
42
  task default: :test
@@ -1,7 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Mint
4
- # Represents a specific currency unit, identified by ISO 4217 alphabetic ocde
2
+ # Represents a specific currency unit, identified by ISO 4217 alphabetic code
5
3
  #
6
4
  # @see https://www.iso.org/iso-4217-currency-codes.html
7
5
  class Currency
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  # :nodoc
4
2
  module Mint
5
3
  def self.money(amount, currency_code)
@@ -30,7 +28,7 @@ module Mint
30
28
  code = code.to_s
31
29
  unless code.match?(/^[A-Z_]+$/)
32
30
  raise ArgumentError,
33
- "Currency code must be String or Symbol ('USD', :EUR)"
31
+ "Currency code must be String or Symbol ('USD', :EUR, 'FUEL', 'MY_COIN')"
34
32
  end
35
33
  if currencies[code]
36
34
  raise KeyError,
@@ -43,19 +41,136 @@ module Mint
43
41
 
44
42
  def self.currencies
45
43
  @currencies ||= {
46
- 'AUD' => Currency.new('AUD', subunit: 2, symbol: '$'),
47
- 'BRL' => Currency.new('BRL', subunit: 2, symbol: 'R$'),
48
- 'CAD' => Currency.new('CAD', subunit: 2, symbol: '$'),
49
- 'CHF' => Currency.new('CHF', subunit: 2, symbol: 'Fr'),
50
- 'CNY' => Currency.new('CNY', subunit: 2, symbol: '¥'),
44
+ # Major Global Currencies
45
+ 'USD' => Currency.new('USD', subunit: 2, symbol: '$'),
51
46
  'EUR' => Currency.new('EUR', subunit: 2, symbol: '€'),
52
47
  'GBP' => Currency.new('GBP', subunit: 2, symbol: '£'),
53
48
  'JPY' => Currency.new('JPY', subunit: 0, symbol: '¥'),
54
- 'MXN' => Currency.new('MXN', subunit: 2, symbol: '$'),
49
+ 'CHF' => Currency.new('CHF', subunit: 2, symbol: 'Fr'),
50
+ 'CAD' => Currency.new('CAD', subunit: 2, symbol: '$'),
51
+ 'AUD' => Currency.new('AUD', subunit: 2, symbol: '$'),
52
+ 'CNY' => Currency.new('CNY', subunit: 2, symbol: '¥'),
53
+ 'SEK' => Currency.new('SEK', subunit: 2, symbol: 'kr'),
55
54
  'NZD' => Currency.new('NZD', subunit: 2, symbol: '$'),
55
+
56
+ # Asia-Pacific
57
+ 'HKD' => Currency.new('HKD', subunit: 2, symbol: 'HK$'),
58
+ 'SGD' => Currency.new('SGD', subunit: 2, symbol: 'S$'),
59
+ 'KRW' => Currency.new('KRW', subunit: 0, symbol: '₩'),
60
+ 'INR' => Currency.new('INR', subunit: 2, symbol: '₹'),
61
+ 'THB' => Currency.new('THB', subunit: 2, symbol: '฿'),
62
+ 'MYR' => Currency.new('MYR', subunit: 2, symbol: 'RM'),
63
+ 'IDR' => Currency.new('IDR', subunit: 2, symbol: 'Rp'),
64
+ 'PHP' => Currency.new('PHP', subunit: 2, symbol: '₱'),
65
+ 'VND' => Currency.new('VND', subunit: 0, symbol: '₫'),
66
+ 'TWD' => Currency.new('TWD', subunit: 2, symbol: 'NT$'),
67
+ 'PKR' => Currency.new('PKR', subunit: 2, symbol: '₨'),
68
+ 'BDT' => Currency.new('BDT', subunit: 2, symbol: '৳'),
69
+ 'LKR' => Currency.new('LKR', subunit: 2, symbol: '₨'),
70
+ 'NPR' => Currency.new('NPR', subunit: 2, symbol: '₨'),
71
+ 'MMK' => Currency.new('MMK', subunit: 2, symbol: 'K'),
72
+ 'KHR' => Currency.new('KHR', subunit: 2, symbol: '៛'),
73
+ 'LAK' => Currency.new('LAK', subunit: 2, symbol: '₭'),
74
+ 'BND' => Currency.new('BND', subunit: 2, symbol: 'B$'),
75
+
76
+ # Middle East & Central Asia
77
+ 'AED' => Currency.new('AED', subunit: 2, symbol: 'د.إ'),
78
+ 'SAR' => Currency.new('SAR', subunit: 2, symbol: '﷼'),
79
+ 'QAR' => Currency.new('QAR', subunit: 2, symbol: '﷼'),
80
+ 'KWD' => Currency.new('KWD', subunit: 3, symbol: 'د.ك'),
81
+ 'BHD' => Currency.new('BHD', subunit: 3, symbol: '.د.ب'),
82
+ 'OMR' => Currency.new('OMR', subunit: 3, symbol: '﷼'),
83
+ 'JOD' => Currency.new('JOD', subunit: 3, symbol: 'د.ا'),
84
+ 'ILS' => Currency.new('ILS', subunit: 2, symbol: '₪'),
85
+ 'TRY' => Currency.new('TRY', subunit: 2, symbol: '₺'),
86
+ 'IRR' => Currency.new('IRR', subunit: 2, symbol: '﷼'),
87
+ 'IQD' => Currency.new('IQD', subunit: 3, symbol: 'د.ع'),
88
+ 'AFN' => Currency.new('AFN', subunit: 2, symbol: '؋'),
89
+ 'KZT' => Currency.new('KZT', subunit: 2, symbol: '₸'),
90
+ 'UZS' => Currency.new('UZS', subunit: 2, symbol: 'лв'),
91
+ 'KGS' => Currency.new('KGS', subunit: 2, symbol: 'лв'),
92
+ 'TJS' => Currency.new('TJS', subunit: 2, symbol: 'SM'),
93
+
94
+ # Europe
95
+ 'NOK' => Currency.new('NOK', subunit: 2, symbol: 'kr'),
96
+ 'DKK' => Currency.new('DKK', subunit: 2, symbol: 'kr'),
97
+ 'ISK' => Currency.new('ISK', subunit: 0, symbol: 'kr'),
98
+ 'PLN' => Currency.new('PLN', subunit: 2, symbol: 'zł'),
99
+ 'CZK' => Currency.new('CZK', subunit: 2, symbol: 'Kč'),
100
+ 'HUF' => Currency.new('HUF', subunit: 2, symbol: 'Ft'),
101
+ 'RON' => Currency.new('RON', subunit: 2, symbol: 'lei'),
102
+ 'BGN' => Currency.new('BGN', subunit: 2, symbol: 'лв'),
103
+ 'HRK' => Currency.new('HRK', subunit: 2, symbol: 'kn'),
104
+ 'RSD' => Currency.new('RSD', subunit: 2, symbol: 'Дин.'),
105
+ 'RUB' => Currency.new('RUB', subunit: 2, symbol: '₽'),
106
+ 'UAH' => Currency.new('UAH', subunit: 2, symbol: '₴'),
107
+ 'BYN' => Currency.new('BYN', subunit: 2, symbol: 'Br'),
108
+ 'MDL' => Currency.new('MDL', subunit: 2, symbol: 'L'),
109
+ 'GEL' => Currency.new('GEL', subunit: 2, symbol: '₾'),
110
+ 'AMD' => Currency.new('AMD', subunit: 2, symbol: '֏'),
111
+ 'AZN' => Currency.new('AZN', subunit: 2, symbol: '₼'),
112
+
113
+ # Africa
114
+ 'ZAR' => Currency.new('ZAR', subunit: 2, symbol: 'R'),
115
+ 'EGP' => Currency.new('EGP', subunit: 2, symbol: '£'),
116
+ 'NGN' => Currency.new('NGN', subunit: 2, symbol: '₦'),
117
+ 'KES' => Currency.new('KES', subunit: 2, symbol: 'KSh'),
118
+ 'GHS' => Currency.new('GHS', subunit: 2, symbol: '¢'),
119
+ 'UGX' => Currency.new('UGX', subunit: 0, symbol: 'USh'),
120
+ 'TZS' => Currency.new('TZS', subunit: 2, symbol: 'TSh'),
121
+ 'ETB' => Currency.new('ETB', subunit: 2, symbol: 'Br'),
122
+ 'MAD' => Currency.new('MAD', subunit: 2, symbol: 'د.م.'),
123
+ 'TND' => Currency.new('TND', subunit: 3, symbol: 'د.ت'),
124
+ 'DZD' => Currency.new('DZD', subunit: 2, symbol: 'د.ج'),
125
+ 'LYD' => Currency.new('LYD', subunit: 3, symbol: 'ل.د'),
126
+ 'AOA' => Currency.new('AOA', subunit: 2, symbol: 'Kz'),
127
+ 'BWP' => Currency.new('BWP', subunit: 2, symbol: 'P'),
128
+ 'NAD' => Currency.new('NAD', subunit: 2, symbol: 'N$'),
129
+ 'SZL' => Currency.new('SZL', subunit: 2, symbol: 'L'),
130
+ 'LSL' => Currency.new('LSL', subunit: 2, symbol: 'L'),
131
+ 'MZN' => Currency.new('MZN', subunit: 2, symbol: 'MT'),
132
+ 'ZMW' => Currency.new('ZMW', subunit: 2, symbol: 'ZK'),
133
+ 'MWK' => Currency.new('MWK', subunit: 2, symbol: 'MK'),
134
+ 'RWF' => Currency.new('RWF', subunit: 0, symbol: 'R₣'),
135
+ 'BIF' => Currency.new('BIF', subunit: 0, symbol: 'FBu'),
136
+
137
+ # Americas
138
+ 'MXN' => Currency.new('MXN', subunit: 2, symbol: '$'),
139
+ 'BRL' => Currency.new('BRL', subunit: 2, symbol: 'R$'),
140
+ 'ARS' => Currency.new('ARS', subunit: 2, symbol: '$'),
141
+ 'CLP' => Currency.new('CLP', subunit: 0, symbol: '$'),
56
142
  'PEN' => Currency.new('PEN', subunit: 2, symbol: 'S/.'),
57
- 'SEK' => Currency.new('SEK', subunit: 2, symbol: 'kr'),
58
- 'USD' => Currency.new('USD', subunit: 2, symbol: '$')
143
+ 'COP' => Currency.new('COP', subunit: 2, symbol: '$'),
144
+ 'VES' => Currency.new('VES', subunit: 2, symbol: 'Bs.'),
145
+ 'UYU' => Currency.new('UYU', subunit: 2, symbol: '$U'),
146
+ 'PYG' => Currency.new('PYG', subunit: 0, symbol: 'Gs'),
147
+ 'BOB' => Currency.new('BOB', subunit: 2, symbol: '$b'),
148
+ 'CRC' => Currency.new('CRC', subunit: 2, symbol: '₡'),
149
+ 'GTQ' => Currency.new('GTQ', subunit: 2, symbol: 'Q'),
150
+ 'HNL' => Currency.new('HNL', subunit: 2, symbol: 'L'),
151
+ 'NIO' => Currency.new('NIO', subunit: 2, symbol: 'C$'),
152
+ 'PAB' => Currency.new('PAB', subunit: 2, symbol: 'B/.'),
153
+ 'DOP' => Currency.new('DOP', subunit: 2, symbol: 'RD$'),
154
+ 'HTG' => Currency.new('HTG', subunit: 2, symbol: 'G'),
155
+ 'JMD' => Currency.new('JMD', subunit: 2, symbol: 'J$'),
156
+ 'TTD' => Currency.new('TTD', subunit: 2, symbol: 'TT$'),
157
+ 'BBD' => Currency.new('BBD', subunit: 2, symbol: 'Bds$'),
158
+ 'BSD' => Currency.new('BSD', subunit: 2, symbol: 'B$'),
159
+ 'BZD' => Currency.new('BZD', subunit: 2, symbol: 'BZ$'),
160
+ 'GYD' => Currency.new('GYD', subunit: 2, symbol: 'G$'),
161
+ 'SRD' => Currency.new('SRD', subunit: 2, symbol: 'Sr$'),
162
+
163
+ # Pacific & Others
164
+ 'FJD' => Currency.new('FJD', subunit: 2, symbol: 'FJ$'),
165
+ 'PGK' => Currency.new('PGK', subunit: 2, symbol: 'K'),
166
+ 'SBD' => Currency.new('SBD', subunit: 2, symbol: 'SI$'),
167
+ 'VUV' => Currency.new('VUV', subunit: 0, symbol: 'VT'),
168
+ 'TOP' => Currency.new('TOP', subunit: 2, symbol: 'T$'),
169
+ 'WST' => Currency.new('WST', subunit: 2, symbol: 'WS$'),
170
+ 'XCD' => Currency.new('XCD', subunit: 2, symbol: 'EC$'),
171
+ 'XOF' => Currency.new('XOF', subunit: 0, symbol: 'CFA'),
172
+ 'XAF' => Currency.new('XAF', subunit: 0, symbol: 'FCFA'),
173
+ 'XPF' => Currency.new('XPF', subunit: 0, symbol: '₣')
59
174
  }
60
175
  end
61
176
  end
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Mint
4
2
  # :nodoc
5
3
  # split and allocation methods
@@ -8,32 +6,30 @@ module Mint
8
6
  raise ArgumentError, 'Need at least 1 proportion element' if proportions.empty?
9
7
 
10
8
  whole = proportions.sum.to_r
11
- allocation = proportions.map { |rate| mint(amount * rate.to_r / whole) }
12
- left_over = self - allocation.sum
13
- allocate_left_over(allocation, left_over)
9
+ amounts = proportions.map! { |rate| (amount * rate.to_r / whole).round(currency.subunit) }
10
+ allocate_left_over!(amounts: amounts, left_over: amount - amounts.sum)
14
11
  end
15
12
 
16
13
  def split(quantity)
17
- unless quantity.positive? && quantity.integer?
14
+ unless quantity.positive? && quantity.integer?
18
15
  raise ArgumentError,
19
16
  'quantity must be an integer > 0'
20
17
  end
21
18
 
22
- fraction = self / quantity
23
- allocation = Array.new(quantity, fraction)
24
- left_over = self - (fraction * quantity)
25
- allocate_left_over(allocation, left_over)
19
+ fraction = (amount / quantity).round(currency.subunit)
20
+ allocate_left_over!(amounts: Array.new(quantity, fraction),
21
+ left_over: amount - (fraction * quantity))
26
22
  end
27
23
 
28
24
  private
29
25
 
30
- def allocate_left_over(allocation, left_over)
31
- minimum = currency.minimum_amount
32
- minimum = mint(left_over.positive? ? minimum : -minimum)
33
-
34
- slots = (left_over / minimum).to_i - 1
35
- (0..slots).each { |slot| allocation[slot] += minimum }
36
- allocation
26
+ def allocate_left_over!(amounts:, left_over:)
27
+ if left_over.nonzero?
28
+ minimum = left_over.positive? ? currency.minimum_amount : -currency.minimum_amount
29
+ last_slot = (left_over / minimum).to_i - 1
30
+ (0..last_slot).each { |slot| amounts[slot] += minimum }
31
+ end
32
+ amounts.map { mint(it) }
37
33
  end
38
34
  end
39
35
  end
@@ -1,31 +1,27 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Mint
4
2
  # :nodoc
5
3
  # Arithmetic functions for money objects
6
4
  class Money
7
- def abs
8
- mint(amount.abs)
9
- end
5
+ def abs = mint(amount.abs)
10
6
 
11
- def negative?
12
- amount.negative?
13
- end
7
+ def negative? = amount.negative?
14
8
 
15
- def positive?
16
- amount.positive?
17
- end
9
+ def positive? = amount.positive?
10
+
11
+ def succ = mint(amount + currency.minimum_amount)
18
12
 
19
13
  def +(addend)
20
- return mint(amount + addend.amount) if same_currency?(addend)
21
- return self unless addend.is_a?(Money) || addend.nonzero?
14
+ return self if addend.respond_to?(:zero?) && addend.zero?
15
+ return mint(amount + addend.amount) if addend.is_a?(Money) && same_currency?(addend)
22
16
 
23
17
  raise TypeError, "#{addend} can't be added to #{self}"
24
18
  end
25
19
 
26
20
  def -(subtrahend)
27
- return self if subtrahend.zero?
28
- return mint(amount - subtrahend.amount) if same_currency?(subtrahend)
21
+ return self if subtrahend.respond_to?(:zero?) && subtrahend.zero?
22
+ if subtrahend.is_a?(Money) && same_currency?(subtrahend)
23
+ return mint(amount - subtrahend.amount)
24
+ end
29
25
 
30
26
  raise TypeError, "#{subtrahend} can't be subtracted from #{self}"
31
27
  end
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Mint
4
2
  # :nodoc
5
3
  # Coercion logic
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Mint
4
2
  # :nodoc
5
3
  # Comparison methods
@@ -8,11 +6,10 @@ module Mint
8
6
 
9
7
  # @return true if both are zero, or both have same amount and same currency
10
8
  def ==(other)
11
- return true if other.is_a?(Numeric) && zero? && other.zero?
9
+ return true if zero? && other.respond_to?(:zero?) && other.zero?
12
10
  return false unless other.is_a?(Mint::Money)
13
- return false if nonzero? && currency != other.currency
14
11
 
15
- amount == other.amount
12
+ amount == other.amount && currency == other.currency
16
13
  end
17
14
 
18
15
  # @example
@@ -38,10 +35,6 @@ module Mint
38
35
  self == other
39
36
  end
40
37
 
41
- def hash
42
- @hash ||= zero? ? 0.hash : [amount, currency].hash
43
- end
44
-
45
38
  def nonzero?
46
39
  amount.nonzero?
47
40
  end
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Mint
4
2
  # Conversion logic
5
3
  class Money
@@ -1,8 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Mint
4
2
  class Money
5
- DEFAULT_FORMAT = '%<symbol>s%<amount>f'
3
+ DEFAULT_FORMAT = '%<symbol>s%<amount>f'.freeze
6
4
 
7
5
  attr_reader :amount, :currency
8
6
 
@@ -26,10 +24,13 @@ module Mint
26
24
  currency.code
27
25
  end
28
26
 
27
+ def hash
28
+ @hash ||= zero? ? 0.hash : [amount, currency.code].hash
29
+ end
30
+
29
31
  # Returns a new Money object with the specified amount, or self if unchanged
30
32
  # @param new_amount [Numeric] The new amount
31
33
  # @return [Money] A new Money object or self
32
-
33
34
  def mint(new_amount)
34
35
  new_amount.to_r == amount ? self : Money.new(new_amount, currency)
35
36
  end
@@ -1,3 +1,3 @@
1
1
  module Minting
2
- VERSION = '0.3.2'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minting
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gilson Ferraz