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 +4 -4
- data/README.md +116 -48
- data/Rakefile +21 -0
- data/lib/minting/mint/currency.rb +1 -3
- data/lib/minting/mint/registry.rb +126 -11
- data/lib/minting/money/allocation.rb +13 -17
- data/lib/minting/money/arithmetics.rb +11 -15
- data/lib/minting/money/coercion.rb +0 -2
- data/lib/minting/money/comparable.rb +2 -9
- data/lib/minting/money/conversion.rb +0 -2
- data/lib/minting/money/money.rb +5 -4
- data/lib/minting/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4dfd2f98840b009208b9cccab86e9ca5d093300351e9e2031e70e03b93315638
|
4
|
+
data.tar.gz: 47a92df548558e20fd7bea3caa10a8966db91341d998e9712405af3d97fe12ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 175607c53124f16cac60c699a6ed6f042b3c3e960055bca1ccb7ba822a05b6cf8401fb66902e148a9ed50b43524ce6e708feaa72cac15f6ddd8b9cf3e9189cc6
|
7
|
+
data.tar.gz: 4fc09433abd38014d0d99c16aed5f88eb36ad039e422b0ffa305d3335da1753a23bae2bfcf775ce6142edf844463ed3942710d87f034375f99e04ff1b7e92812
|
data/README.md
CHANGED
@@ -1,61 +1,63 @@
|
|
1
1
|
# Minting
|
2
2
|
|
3
|
-
|
3
|
+
Fast, precise, and developer-friendly money handling for Ruby.
|
4
4
|
|
5
|
-
|
5
|
+
[](https://badge.fury.io/rb/minting)
|
6
6
|
|
7
|
-
##
|
7
|
+
## Why Minting?
|
8
8
|
|
9
|
-
|
9
|
+
**Tired of floating-point errors in financial calculations?** Minting uses Rational numbers for perfect precision.
|
10
10
|
|
11
|
-
|
12
|
-
bundle add minting
|
13
|
-
bundle install
|
14
|
-
```
|
11
|
+
**Need performance?** Minting is 2x faster than alternatives.
|
15
12
|
|
16
|
-
|
13
|
+
**Want a clean API?** Minting provides an intuitive interface with helpful error messages.
|
17
14
|
|
18
|
-
|
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
|
-
|
17
|
+
**Rails**? Use the [minting-rails](https://github.com/gferraz/minting-rails) companion gem
|
18
|
+
|
19
|
+
## Quick start
|
23
20
|
|
24
21
|
```ruby
|
25
|
-
|
26
|
-
```
|
22
|
+
require 'minting'
|
27
23
|
|
28
|
-
|
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
|
-
|
31
|
-
|
28
|
+
total.to_s #=> "$21.59"
|
29
|
+
total.currency_code #=> "USD"
|
32
30
|
```
|
33
31
|
|
34
|
-
|
32
|
+
## Features
|
35
33
|
|
36
|
-
|
37
|
-
|
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
|
-
#
|
46
|
-
|
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
|
-
#
|
51
|
-
|
50
|
+
# Create money using Numeric refinements
|
51
|
+
using Mint
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
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
|
-
#
|
84
|
+
# Proportional allocation and split
|
83
85
|
|
84
|
-
|
85
|
-
|
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
|
-
|
89
|
+
# Ranges and enumeration are supported
|
88
90
|
|
89
|
-
|
90
|
-
|
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
|
-
|
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
|
-
|
105
|
+
Option 2: add the line below to your application's Gemfile:
|
100
106
|
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
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
|
-
|
47
|
-
'
|
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
|
-
'
|
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
|
-
'
|
58
|
-
'
|
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
|
-
|
12
|
-
left_over
|
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
|
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 =
|
23
|
-
|
24
|
-
|
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(
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
17
|
-
|
9
|
+
def positive? = amount.positive?
|
10
|
+
|
11
|
+
def succ = mint(amount + currency.minimum_amount)
|
18
12
|
|
19
13
|
def +(addend)
|
20
|
-
return
|
21
|
-
return
|
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
|
-
|
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
|
# 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.
|
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
|
data/lib/minting/money/money.rb
CHANGED
@@ -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
|
data/lib/minting/version.rb
CHANGED