Skip to content

Commit

Permalink
Allow to customize allocation precision
Browse files Browse the repository at this point in the history
  • Loading branch information
faraquet committed Oct 11, 2024
1 parent 870bac4 commit 946a25e
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 10 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,4 @@ Zubin Henner
Бродяной Александр
Nicolay Hvidsten
Simon Neutert
Andrei Andriichuk
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- **Potential breaking change**: Fix USDC decimals places from 2 to 6
- Fix typo in ILS currency
- Enable customizable allocation precision

## 6.19.0

Expand Down
18 changes: 13 additions & 5 deletions lib/money/money/allocation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

class Money
class Allocation
# Splits a given amount in parts. The allocation is based on the parts' proportions
# or evenly if parts are numerically specified.
# Allocates a specified amount into parts based on their proportions or distributes
# it evenly when the number of parts is specified numerically.
#
# The results should always add up to the original amount.
# The total of the allocated amounts will always equal the original amount.
#
# The parts can be specified as:
# Numeric — performs the split between a given number of parties evenly
Expand All @@ -14,10 +14,12 @@ class Allocation
# @param amount [Numeric] The total amount to be allocated.
# @param parts [Numeric, Array<Numeric>] Number of parts to split into or an array (proportions for allocation)
# @param whole_amounts [Boolean] Specifies whether to allocate whole amounts only. Defaults to true.
# @param precision [Integer] The number of decimal places to round to.
# This is ignored if whole_amounts is set to true.
#
# @return [Array<Numeric>] An array containing the allocated amounts.
# @raise [ArgumentError] If parts is empty or not provided.
def self.generate(amount, parts, whole_amounts = true)
def self.generate(amount, parts, whole_amounts = true, precision = nil)
parts = if parts.is_a?(Numeric)
Array.new(parts, 1)
elsif parts.all?(&:zero?)
Expand All @@ -43,7 +45,13 @@ def self.generate(amount, parts, whole_amounts = true)
current_split = 0
if parts_sum > 0
current_split = remaining_amount * part / parts_sum
current_split = current_split.truncate if whole_amounts
current_split = if whole_amounts
current_split.truncate
elsif precision
current_split.round(precision)
else
current_split
end
end

result.unshift current_split
Expand Down
14 changes: 11 additions & 3 deletions sig/lib/money/money/allocation.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@ class Money
# The results should always add up to the original amount.
#
# The parts can be specified as:
# Numeric — performs the split between a given number of parties evenely
# Numeric — performs the split between a given number of parties evenly
# Array<Numeric> — allocates the amounts proportionally to the given array
#
def self.generate: (untyped amount, (Numeric | Array[Numeric]) parts, ?bool whole_amounts) -> untyped
# @param amount [Numeric] The total amount to be allocated.
# @param parts [Numeric, Array<Numeric>] Number of parts to split into or an array (proportions for allocation).
# @param whole_amounts [Boolean] Specifies whether to allocate whole amounts only. Defaults to true.
# @param precision [Integer] The number of decimal places to round to.
# This is ignored if whole_amounts is set to true.
#
# @return [Array<Numeric>] An array containing the allocated amounts.
# @raise [ArgumentError] If parts is empty or not provided.
def self.generate: (Numeric amount, (Numeric | Array[Numeric]) parts, ?bool whole_amounts, ?Integer precision) -> Array[Numeric]
end
end
end
38 changes: 36 additions & 2 deletions spec/money/allocation_spec.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# encoding: utf-8

describe Money::Allocation do
describe 'given number as argument' do
it 'raises an error when invalid argument is given' do
describe 'given number as argument' do
it 'raises an error when invalid argument is given' do
expect { described_class.generate(100, 0) }.to raise_error(ArgumentError)
expect { described_class.generate(100, -1) }.to raise_error(ArgumentError)
end
Expand Down Expand Up @@ -151,5 +151,39 @@
expect(result.reduce(&:+)).to eq(amount)
expect(result).to eq([-61566, -61565, -61565, -61565, -61565, -61565, -61565, -60953, -52091, -52091, -52091, -52091])
end

context 'when specified precision' do
let(:amount) { 246.4 }
let(:allocations) do
[
81.29, 81.29, 81.29, 81.29,
234.8, 234.8, 234.8, 234.8,
90.36, 90.36, 90.36, 90.36,
90.36, 90.36, 90.36, 90.36,
90.36, 90.36, 90.36, 90.36,
90.36, 90.36, 90.36, 90.36,
90.36, 90.36, 90.36, 90.36,
90.36
]
end

it 'allocates with required precision' do
result = described_class.generate(amount, allocations, false, 16)
expect(result.reduce(&:+)).to eq(amount)

expected = %w[
6.3347130857200688 6.3347130857200689 6.3347130857200688 6.3347130857200688
18.2973383260803562 18.2973383260803563 18.2973383260803563 18.2973383260803563
7.0415140167999191 7.041514016799919 7.0415140167999191 7.041514016799919
7.0415140167999191 7.041514016799919 7.0415140167999191 7.041514016799919
7.0415140167999191 7.041514016799919 7.0415140167999191 7.041514016799919
7.041514016799919 7.041514016799919 7.041514016799919 7.041514016799919
7.041514016799919 7.041514016799919 7.041514016799919 7.041514016799919
7.041514016799919
].map { |element| BigDecimal(element) }

expect(result).to eq(expected)
end
end
end
end

0 comments on commit 946a25e

Please sign in to comment.