minting 1.1.1 → 1.2.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: ef2574a0bffcbccb882faba1a839ef37369e047b23becf1ce405d2c1b883322a
4
- data.tar.gz: 19ec197c01ceb083a2f6dcf2c6898b38f80ba7679749154b74b3d9fb2f2ffeda
3
+ metadata.gz: b05d1cbbf6c714102f38f165effba77384e4fc5cebf691d4b9ab75082c22b992
4
+ data.tar.gz: 2d5bb5aeb6e527a2d4419bcca35d79c426709797975e9ec38217558d875ea9b8
5
5
  SHA512:
6
- metadata.gz: e8680da52ba5417cbe89f2c36615247fa1cc7302088c7d087aa782b6d1958bcd124a38f2b1027cd164810f9f04b02974cf8ac68cb61047d204c9189bf1943a6d
7
- data.tar.gz: e85e786f6cb1f98b10e75742097651013e9fe57ddad4896a70fd85b836709e2eecfe26d0f0854b05423f931b1b5fc17b46dfa63c0e25f1a86ffa024de3748d05
6
+ metadata.gz: ce7311d97cbe60c2f71ba5f65d1313704084eebae842e012c2cd857230055ef56007575e8f89887f8ae157c004d205e43370ada8d2fc2b5f3ea00b7c21c8017a
7
+ data.tar.gz: e4903099bb997667472ddc0c26ff07b3894bb9fa5122221c94045d84f2699e28d203646429e341a8119177a07212991f04d36c5806a12c8388766a3a8aebb6eb
data/README.md CHANGED
@@ -91,6 +91,11 @@ price_in_euros.to_s(format: '%<symbol>2s%<amount>+10f') #=> " € +12.34"
91
91
 
92
92
  price.to_json # => "{ "currency": "USD", "amount": "9.99" }"
93
93
 
94
+ # Hash conversion
95
+
96
+ price.to_hash #=> {currency: "USD", amount: "9.99"}
97
+
98
+
94
99
  # Proportional allocation and split
95
100
 
96
101
  ten.split(3) #=> [[USD 3.34], [USD 3.33], [USD 3.33]]
@@ -192,11 +197,6 @@ This gem includes a performance suite under `test/performance`:
192
197
  - Memory and GC pressure tests
193
198
  - Competitive benchmarks vs `money` gem
194
199
 
195
- On a typical machine, reference numbers are:
196
-
197
- - Money creation: ~1.6M ops/sec
198
- - Addition: ~1.7M ops/sec
199
-
200
200
  Run locally:
201
201
 
202
202
  ```bash
@@ -210,6 +210,49 @@ BENCH=true rake bench:competitive
210
210
  rake bench:regression
211
211
  ```
212
212
 
213
+ ## Benchmark Summary: Minting vs Money Gem
214
+
215
+ Generated by Qwen from the latest benchmark run on Ruby 4.0.1. - 2026-05-30
216
+
217
+ ### Key Takeaways
218
+
219
+ - **Mint is consistently faster** than the Money gem across all measured operations.
220
+ - **Mint is 2.28x faster** in the 50,000-transaction simulation.
221
+ - **Mint object creation is 2.76x faster** than `Money.from_amount`.
222
+ - In formatting and conversion, Mint is often **10+x **.
223
+ - Mint’s performance advantage is especially strong for numeric conversion, string formatting, comparisons, and high-volume transaction loops.
224
+
225
+ ### Performance Highlights
226
+
227
+ | Category | Mint | Money | Approx. Ratio |
228
+ | --- | --- | --- | --- |
229
+ | High-volume transactions | 195,412 ops/sec | 85,882 ops/sec | 2.28x faster |
230
+ | `Mint.money` creation | 1.14M ops/sec | — | 2.76x faster than `Money.from_amount` |
231
+ | `some.dollars` creation | 990k ops/sec | — | 1.15x faster than `Mint.money` |
232
+ | `Money.new` creation | — | 715k ops/sec | Mint 1.59x faster |
233
+ | `to_f` formatting | 8.8M–9.3M ops/sec | 0.7M ops/sec | ~12x faster |
234
+ | `to_d` conversion | 2.1M–2.3M ops/sec | 0.73M–0.79M ops/sec | ~3x faster |
235
+ | `to_s` formatting | 300k–420k ops/sec | 109k–132k ops/sec | ~3x faster |
236
+ | `inspect` formatting | ~2.6–2.9M ops/sec | ~1.1–1.16M ops/sec | ~2.5x faster |
237
+ | `to_json` formatting | ~2.0–2.2M ops/sec | ~110k–126k ops/sec | ~17x faster |
238
+ | Currency lookup `Mint.currency('USD')` | 3.82M ops/sec | — | 1.60x faster than `Money::Currency.new` |
239
+ | Currency lookup `Money::Currency.find('USD')` | 3.63M ops/sec | 1.67M ops/sec | 2.29x faster |
240
+ | Addition | 1.11M ops/sec | 0.37M ops/sec | 3.0x faster |
241
+ | Subtraction | 1.11M ops/sec | 0.36M ops/sec | 3.0x faster |
242
+ | Multiplication | 1.28M ops/sec | 0.51M ops/sec | 2.5x faster |
243
+ | Division | 1.04M ops/sec | 0.37M ops/sec | 2.8x faster |
244
+ | Ratio division | 2.94M ops/sec | 0.39M ops/sec | 7.6x faster |
245
+ | Comparison (`==`, `<`, `>`) | 2.5M–4.1M ops/sec | 0.35M–0.38M ops/sec | 7x–10x faster |
246
+ | Allocation (`Mint.allocate`) | 279k ops/sec | 146k ops/sec | 1.9x faster |
247
+ | Split (`Mint.split`) | 215k ops/sec | 85k ops/sec | 3.3x faster |
248
+
249
+ ### Commands Used
250
+
251
+ ```sh
252
+ BENCH=true bundle exec ruby -Ilib:test -r ./test/test_helper.rb test/performance/competitive_performance_benchmark.rb
253
+ BENCH=true bundle exec ruby -Ilib:test -r ./test/test_helper.rb test/performance/competitive_memory_benchmark.rb
254
+ ```
255
+
213
256
  ## License
214
257
 
215
258
  MIT
data/Rakefile CHANGED
@@ -12,15 +12,20 @@ Rake::TestTask.new(:test) do |t|
12
12
  t.ruby_opts << '-rtest_helper.rb'
13
13
  end
14
14
 
15
- Rake::TestTask.new(:bench) do |t|
15
+ Rake::TestTask.new('bench') do |t|
16
16
  t.libs = %w[lib test]
17
- t.pattern = 'test/**/*_benchmark.rb'
17
+ t.pattern = 'test/performance/*_benchmark.rb'
18
18
  end
19
19
 
20
- # Performance benchmark tasks
21
- Rake::TestTask.new('bench:performance') do |t|
20
+ Rake::TestTask.new('bench:parse') do |t|
22
21
  t.libs = %w[lib test]
23
- t.pattern = 'test/performance/*_benchmark.rb'
22
+ t.pattern = 'test/performance/parse_benchmark.rb'
23
+ t.ruby_opts << '-r test_helper.rb'
24
+ end
25
+
26
+ Rake::TestTask.new('bench:edge') do |t|
27
+ t.libs = %w[lib test]
28
+ t.pattern = 'test/performance/algorithm_benchmark.rb'
24
29
  t.ruby_opts << '-r test_helper.rb'
25
30
  end
26
31
 
@@ -32,12 +37,10 @@ end
32
37
 
33
38
  Rake::TestTask.new('bench:competitive') do |t|
34
39
  t.libs = %w[lib test]
35
- t.pattern = 'test/performance/competitive_benchmark.rb'
40
+ t.pattern = 'test/performance/competitive_performance_benchmark.rb'
36
41
  t.ruby_opts << '-r test_helper.rb'
37
42
  end
38
43
 
39
- task 'bench:all' => ['bench', 'bench:performance']
40
-
41
44
  RuboCop::RakeTask.new(:cop)
42
45
 
43
46
  YARD::Rake::YardocTask.new do |t|
@@ -3,7 +3,10 @@ module Mint
3
3
  #
4
4
  # @see https://www.iso.org/iso-4217-currency-codes.html
5
5
  class Currency
6
- attr_reader :code, :subunit, :symbol, :priority, :minimum_amount, :country, :name
6
+ attr_reader :code, :subunit, :symbol,
7
+ :country,
8
+ :fractional_multiplier, :minimum_amount,
9
+ :name, :priority
7
10
 
8
11
  def inspect
9
12
  "<Currency:(#{code} #{symbol} #{subunit})>"
@@ -18,7 +21,8 @@ module Mint
18
21
  @priority = priority.to_i
19
22
  @country = country
20
23
  @name = name
21
- @minimum_amount = 10r**-@subunit
24
+ @fractional_multiplier = 10**@subunit
25
+ @minimum_amount = 1r / fractional_multiplier
22
26
  freeze
23
27
  end
24
28
  end
@@ -14,6 +14,9 @@ module Mint
14
14
  raise ArgumentError, "Currency [#{currency_code}] not registered. Check Mint.currencies"
15
15
  end
16
16
 
17
+ # Returns default zero, no currency money
18
+ def self.zero = @zero ||= Money.new(0, Mint.currency('XXX'))
19
+
17
20
  # Finds a registered currency by its code, symbol,
18
21
  # or retrieves it directly if already a Currency object.
19
22
  #
@@ -34,11 +37,11 @@ module Mint
34
37
  #
35
38
  # @param code [String, Symbol] the unique currency code (e.g. 'USD', :EUR)
36
39
  # @param subunit [Integer] the decimal subunit precision (defaults to 2)
37
- # @param symbol [String] the display symbol (defaults to '$')
40
+ # @param symbol [String] the display symbol (defaults to '')
38
41
  # @param priority [Integer] parser precedence priority (defaults to 0)
39
42
  # @return [Currency] the registered or existing Currency instance
40
43
  # @raise [ArgumentError] if the code layout is invalid or register throws an error
41
- def self.register_currency(code:, subunit: 2, symbol: '$', priority: 0)
44
+ def self.register_currency(code:, subunit: 2, symbol: '', priority: 0)
42
45
  code = code.to_s
43
46
  currencies[code] || register_currency!(code:, subunit:, symbol:, priority:)
44
47
  end
@@ -14,7 +14,8 @@ module Mint
14
14
  raise ArgumentError, 'Need at least 1 proportion element' if proportions.empty?
15
15
  raise ArgumentError, 'Proportions total must not be zero' if whole.zero?
16
16
 
17
- amounts = proportions.map { |rate| (amount * rate.to_r / whole).round(currency.subunit) }
17
+ subunit = currency.subunit
18
+ amounts = proportions.map { |rate| (amount * rate.to_r / whole).round(subunit) }
18
19
  allocate_left_over!(amounts: amounts, left_over: amount - amounts.sum)
19
20
  end
20
21
 
@@ -3,7 +3,7 @@ module Mint
3
3
  # Returns the absolute value of the monetary amount as a new {Money} instance.
4
4
  #
5
5
  # @return [Money] the absolute value
6
- def abs = mint(amount.abs)
6
+ def abs = mint(amount.abs)
7
7
 
8
8
  # Returns true if the monetary amount is less than zero.
9
9
  #
@@ -39,15 +39,17 @@ module Mint
39
39
  amount.to_i
40
40
  end
41
41
 
42
+ def to_hash
43
+ { currency: currency_code, amount: Kernel.format("%0.#{currency.subunit}f", amount) }
44
+ end
45
+
42
46
  # Serializes the money instance to a standard JSON object containing the amount and currency.
43
47
  # Highly optimized to run without external dependencies.
44
48
  #
45
49
  # @return [String] the JSON serialized string representation
46
50
  def to_json(*_args)
47
- subunit = currency.subunit
48
51
  Kernel.format(
49
- %({"currency": "#{currency_code}", "amount": "%0.#{subunit}f"}),
50
- amount
52
+ %({"currency": "#{currency_code}", "amount": "%0.#{currency.subunit}f"}), amount
51
53
  )
52
54
  end
53
55
 
@@ -24,6 +24,8 @@ module Mint
24
24
  # @return [String] the ISO currency code
25
25
  def currency_code = currency.code
26
26
 
27
+ def fractional = (amount * currency.fractional_multiplier).to_i
28
+
27
29
  # Generates a stable hash key for Money instances.
28
30
  #
29
31
  # @return [Integer] the calculated hash value
@@ -33,7 +35,7 @@ module Mint
33
35
  # @param new_amount [Numeric] The new amount
34
36
  # @return [Money] A new Money object or self
35
37
  def mint(new_amount)
36
- new_amount.to_r == amount ? self : Money.new(new_amount, currency)
38
+ new_amount == amount ? self : Money.new(new_amount, currency)
37
39
  end
38
40
 
39
41
  # Returns a standard developer-oriented string inspection of the Money object.
@@ -48,8 +50,5 @@ module Mint
48
50
  # @param other [Object] the target object to compare
49
51
  # @return [Boolean] true if currencies match, false otherwise
50
52
  def same_currency?(other) = other.respond_to?(:currency) && other.currency == currency
51
-
52
- # Returns default zero no currency money
53
- def self.zero = @zero ||= new(0, Mint.currencies('XXX'))
54
53
  end
55
54
  end
@@ -32,7 +32,7 @@ module Mint
32
32
  # Extracts a numeric value from input that should only contain an amount.
33
33
  def self.parse_amount(input)
34
34
  # Remove any charater that is not a digit, comma or period
35
- numeric = input.scan(/[\d.\-,]/).join
35
+ numeric = input.scan(/[\d.,-]/).join
36
36
  numeric = normalize_separators(numeric)
37
37
  Rational(numeric)
38
38
  end
@@ -1,5 +1,5 @@
1
1
  # Root namespace for the Minting library.
2
2
  module Minting
3
3
  # Current version of the Minting gem.
4
- VERSION = '1.1.1'.freeze
4
+ VERSION = '1.2.0'.freeze
5
5
  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: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gilson Ferraz