22
33class Money
44 class Allocation
5- # Splits a given amount in parts without losing pennies.
6- # The left-over pennies will be distributed round-robin amongst the parts. This means that
7- # parts listed first will likely receive more pennies than the ones listed later.
5+ # Splits a given amount in parts. The allocation is based on the parts' proportions
6+ # or evenly if parts are numerically specified.
87 #
98 # The results should always add up to the original amount.
109 #
1110 # The parts can be specified as:
1211 # Numeric — performs the split between a given number of parties evenly
1312 # Array<Numeric> — allocates the amounts proportionally to the given array
1413 #
14+ # @param amount [Numeric] The total amount to be allocated.
15+ # @param parts [Numeric, Array<Numeric>] Number of parts to split into or an array (proportions for allocation)
16+ # @param whole_amounts [Boolean] Specifies whether to allocate whole amounts only. Defaults to true.
17+ #
18+ # @return [Array<Numeric>] An array containing the allocated amounts.
19+ # @raise [ArgumentError] If parts is empty or not provided.
1520 def self . generate ( amount , parts , whole_amounts = true )
1621 parts = if parts . is_a? ( Numeric )
1722 Array . new ( parts , 1 )
@@ -21,7 +26,12 @@ def self.generate(amount, parts, whole_amounts = true)
2126 parts . dup
2227 end
2328
24- raise ArgumentError , 'need at least one party' if parts . empty?
29+ raise ArgumentError , 'need at least one part' if parts . empty?
30+
31+ if [ amount , *parts ] . any? { |i | i . is_a? ( BigDecimal ) || i . is_a? ( Float ) || i . is_a? ( Rational ) }
32+ amount = convert_to_big_decimal ( amount )
33+ parts . map! { |p | convert_to_big_decimal ( p ) }
34+ end
2535
2636 result = [ ]
2737 remaining_amount = amount
@@ -40,29 +50,23 @@ def self.generate(amount, parts, whole_amounts = true)
4050 remaining_amount -= current_split
4151 end
4252
43- ## round-robin allocation of any remaining pennies
44- if result . size > 0
45- while remaining_amount != 0
46- index = 0
47-
48- amount_to_distribute = [ 1 , remaining_amount . abs ] . min
49-
50- if remaining_amount > 0
51- result [ index ] += amount_to_distribute
52- remaining_amount -= amount_to_distribute
53- else
54- result [ index ] -= amount_to_distribute
55- remaining_amount += amount_to_distribute
56- end
53+ result
54+ end
5755
58- index += 1
59- if index > result . size
60- index = 0
61- end
62- end
56+ # Converts a given number to BigDecimal.
57+ # This method supports inputs of BigDecimal, Rational, and other numeric types by ensuring they are all returned
58+ # as BigDecimal instances for consistent handling.
59+ #
60+ # @param number [Numeric, BigDecimal, Rational] The number to convert.
61+ # @return [BigDecimal] The converted number as a BigDecimal.
62+ def self . convert_to_big_decimal ( number )
63+ if number . is_a? BigDecimal
64+ number
65+ elsif number . is_a? Rational
66+ BigDecimal ( number . to_f . to_s )
67+ else
68+ BigDecimal ( number . to_s )
6369 end
64-
65- result
6670 end
6771 end
6872end
0 commit comments