Skip to content

Commit 86dadae

Browse files
committed
Allow to to customize allocation precision
1 parent 870bac4 commit 86dadae

File tree

5 files changed

+74
-10
lines changed

5 files changed

+74
-10
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,4 @@ Zubin Henner
141141
Бродяной Александр
142142
Nicolay Hvidsten
143143
Simon Neutert
144+
Andrei Andriichuk

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- **Potential breaking change**: Fix USDC decimals places from 2 to 6
66
- Fix typo in ILS currency
7+
- Enable and enforce customizable allocation precision for handling large arrays
78

89
## 6.19.0
910

lib/money/money/allocation.rb

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
class Money
44
class Allocation
5-
# Splits a given amount in parts. The allocation is based on the parts' proportions
6-
# or evenly if parts are numerically specified.
5+
# Allocates a specified amount into parts based on their proportions or distributes
6+
# it evenly when the number of parts is specified numerically.
77
#
8-
# The results should always add up to the original amount.
8+
# The total of the allocated amounts will always equal the original amount.
99
#
1010
# The parts can be specified as:
1111
# Numeric — performs the split between a given number of parties evenly
@@ -14,10 +14,12 @@ class Allocation
1414
# @param amount [Numeric] The total amount to be allocated.
1515
# @param parts [Numeric, Array<Numeric>] Number of parts to split into or an array (proportions for allocation)
1616
# @param whole_amounts [Boolean] Specifies whether to allocate whole amounts only. Defaults to true.
17+
# @param precision [Integer] The number of decimal places to round to.
18+
# This is ignored if whole_amounts is set to true. Defaults to BigDecimal.double_fig.
1719
#
1820
# @return [Array<Numeric>] An array containing the allocated amounts.
1921
# @raise [ArgumentError] If parts is empty or not provided.
20-
def self.generate(amount, parts, whole_amounts = true)
22+
def self.generate(amount, parts, whole_amounts = true, precision = BigDecimal.double_fig)
2123
parts = if parts.is_a?(Numeric)
2224
Array.new(parts, 1)
2325
elsif parts.all?(&:zero?)
@@ -43,7 +45,7 @@ def self.generate(amount, parts, whole_amounts = true)
4345
current_split = 0
4446
if parts_sum > 0
4547
current_split = remaining_amount * part / parts_sum
46-
current_split = current_split.truncate if whole_amounts
48+
current_split = whole_amounts ? current_split.truncate : current_split.round(precision)
4749
end
4850

4951
result.unshift current_split

sig/lib/money/money/allocation.rbs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,17 @@ class Money
77
# The results should always add up to the original amount.
88
#
99
# The parts can be specified as:
10-
# Numeric — performs the split between a given number of parties evenely
10+
# Numeric — performs the split between a given number of parties evenly
1111
# Array<Numeric> — allocates the amounts proportionally to the given array
1212
#
13-
def self.generate: (untyped amount, (Numeric | Array[Numeric]) parts, ?bool whole_amounts) -> untyped
13+
# @param amount [Numeric] The total amount to be allocated.
14+
# @param parts [Numeric, Array<Numeric>] Number of parts to split into or an array (proportions for allocation).
15+
# @param whole_amounts [Boolean] Specifies whether to allocate whole amounts only. Defaults to true.
16+
# @param precision [Integer] The number of decimal places to round to.
17+
# This is ignored if whole_amounts is set to true. Defaults to BigDecimal.double_fig.
18+
#
19+
# @return [Array<Numeric>] An array containing the allocated amounts.
20+
# @raise [ArgumentError] If parts is empty or not provided.
21+
def self.generate: (Numeric amount, (Numeric | Array[Numeric]) parts, ?bool whole_amounts, ?Integer precision) -> Array[Numeric]
1422
end
15-
end
23+
end

spec/money/allocation_spec.rb

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# encoding: utf-8
22

33
describe Money::Allocation do
4-
describe 'given number as argument' do
5-
it 'raises an error when invalid argument is given' do
4+
describe 'given number as argument' do
5+
it 'raises an error when invalid argument is given' do
66
expect { described_class.generate(100, 0) }.to raise_error(ArgumentError)
77
expect { described_class.generate(100, -1) }.to raise_error(ArgumentError)
88
end
@@ -151,5 +151,57 @@
151151
expect(result.reduce(&:+)).to eq(amount)
152152
expect(result).to eq([-61566, -61565, -61565, -61565, -61565, -61565, -61565, -60953, -52091, -52091, -52091, -52091])
153153
end
154+
155+
context 'when large arrays' do
156+
let(:amount) { 246.4 }
157+
let(:allocations) do
158+
[
159+
81.29, 81.29, 81.29, 81.29,
160+
234.8, 234.8, 234.8, 234.8,
161+
90.36, 90.36, 90.36, 90.36,
162+
90.36, 90.36, 90.36, 90.36,
163+
90.36, 90.36, 90.36, 90.36,
164+
90.36, 90.36, 90.36, 90.36,
165+
90.36, 90.36, 90.36, 90.36,
166+
90.36
167+
]
168+
end
169+
170+
it 'allocates with default precision BigDecimal.double_fig' do
171+
result = described_class.generate(amount, allocations, false)
172+
expect(result.reduce(&:+)).to eq(amount)
173+
174+
expected = %w[
175+
6.3347130857200688 6.3347130857200689 6.3347130857200688 6.3347130857200688
176+
18.2973383260803562 18.2973383260803563 18.2973383260803563 18.2973383260803563
177+
7.0415140167999191 7.041514016799919 7.0415140167999191 7.041514016799919
178+
7.0415140167999191 7.041514016799919 7.0415140167999191 7.041514016799919
179+
7.0415140167999191 7.041514016799919 7.0415140167999191 7.041514016799919
180+
7.041514016799919 7.041514016799919 7.041514016799919 7.041514016799919
181+
7.041514016799919 7.041514016799919 7.041514016799919 7.041514016799919
182+
7.041514016799919
183+
].map { |element| BigDecimal(element) }
184+
185+
expect(result).to eq(expected)
186+
end
187+
188+
it 'allows to customize precision' do
189+
result = described_class.generate(amount, allocations, false, 4)
190+
expect(result.reduce(&:+)).to eq(amount)
191+
192+
expected = [
193+
6.3347, 6.3348, 6.3347, 6.3347,
194+
18.2974, 18.2974, 18.2974, 18.2974,
195+
7.0415, 7.0415, 7.0415, 7.0415,
196+
7.0415, 7.0415, 7.0415, 7.0415,
197+
7.0415, 7.0415, 7.0415, 7.0415,
198+
7.0415, 7.0415, 7.0415, 7.0415,
199+
7.0415, 7.0415, 7.0415, 7.0415,
200+
7.0415
201+
]
202+
203+
expect(result).to eq(expected)
204+
end
205+
end
154206
end
155207
end

0 commit comments

Comments
 (0)