lib/money/money/allocation.rb in money-6.18.0 vs lib/money/money/allocation.rb in money-6.19.0
- old
+ new
@@ -1,30 +1,36 @@
# encoding: utf-8
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)
elsif parts.all?(&:zero?)
Array.new(parts.count, 1)
else
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
until parts.empty? do
parts_sum = parts.inject(0, :+)
@@ -39,8 +45,24 @@
result.unshift current_split
remaining_amount -= current_split
end
result
+ 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
end
end
end