Skip to content

Commit

Permalink
Merge pull request #1082 from tirosh/increased_precision
Browse files Browse the repository at this point in the history
Improves Precision and Simplifies Allocation Logic
  • Loading branch information
semmons99 authored Mar 6, 2024
2 parents 092ecc7 + 4dfce9e commit d7e7f45
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 29 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ Thomas E Enebo
Thomas Weymuth
Ticean Bennett
Tien Nguyen
Till Grosch
Tim Hart
Tim Krins
Tobias Luetke
Expand Down
56 changes: 28 additions & 28 deletions lib/money/money/allocation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

class Money
class Allocation
# Splits a given amount in parts without losing pennies.
# The left-over pennies will be distributed round-robin amongst the parts. This means that
# parts listed first will likely receive more pennies than the ones listed later.
# Splits a given amount in parts. The allocation is based on the parts' proportions
# or evenly if parts are numerically specified.
#
# 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
# Array<Numeric> — allocates the amounts proportionally to the given array
# @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.
#
# @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)
parts = if parts.is_a?(Numeric)
Array.new(parts, 1)
Expand All @@ -21,7 +22,12 @@ def self.generate(amount, parts, whole_amounts = true)
parts.dup
end

raise ArgumentError, 'need at least one party' if parts.empty?
raise ArgumentError, 'need at least one part' if parts.empty?

if [amount, *parts].any? { |i| i.is_a?(BigDecimal) || i.is_a?(Float) || i.is_a?(Rational) }
amount = convert_to_big_decimal(amount)
parts.map! { |p| convert_to_big_decimal(p) }
end

result = []
remaining_amount = amount
Expand All @@ -40,29 +46,23 @@ def self.generate(amount, parts, whole_amounts = true)
remaining_amount -= current_split
end

## round-robin allocation of any remaining pennies
if result.size > 0
while remaining_amount != 0
index = 0

amount_to_distribute = [1, remaining_amount.abs].min

if remaining_amount > 0
result[index] += amount_to_distribute
remaining_amount -= amount_to_distribute
else
result[index] -= amount_to_distribute
remaining_amount += amount_to_distribute
end
result
end

index += 1
if index > result.size
index = 0
end
end
# Converts a given number to BigDecimal.
# This method supports inputs of BigDecimal, Rational, and other numeric types by ensuring they are all returned
# as BigDecimal instances for consistent handling.
#
# @param number [Numeric, BigDecimal, Rational] The number to convert.
# @return [BigDecimal] The converted number as a BigDecimal.
def self.convert_to_big_decimal(number)
if number.is_a? BigDecimal
number
elsif number.is_a? Rational
BigDecimal(number.to_f.to_s)
else
BigDecimal(number.to_s)
end

result
end
end
end
2 changes: 1 addition & 1 deletion spec/money/formatting_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,7 @@

context 'when symbol_position is passed' do
it "inserts currency symbol before the amount when set to :before" do
expect(Money.new(100_00, 'CHF').format(symbol_position: :before)).to eq "CHF 100.00"
expect(Money.new(100_00, 'CHF').format(symbol_position: :before)).to eq "CHF100.00"
end

it "inserts currency symbol after the amount when set to :after" do
Expand Down

0 comments on commit d7e7f45

Please sign in to comment.