module LareRound
Constants
- VERSION
Public Class Methods
round(values, precision)
click to toggle source
# File lib/lare_round.rb, line 6 def self.round(values, precision) # although it is the senders responsibility to ensure that correct messages # are sent to this module it might not be quite obvious so i provide some # help here with errors if input is invalid array_of_values = values.is_a?(Hash) ? values.values : values handle_value_errors(array_of_values) handle_precision_errors(precision) process(values, precision) end
Private Class Methods
handle_precision_errors(precision)
click to toggle source
# File lib/lare_round.rb, line 63 def handle_precision_errors(precision) raise LareRoundError, 'precision must not be nil' if precision.nil? unless precision.is_a? Numeric raise LareRoundError, 'precision must be a number' end if precision.negative? raise LareRoundError, 'precision must be greater or equal to 0' end end
handle_value_errors(values)
click to toggle source
# File lib/lare_round.rb, line 40 def handle_value_errors(values) raise LareRoundError, 'values must not be nil' if values.nil? raise LareRoundError, 'values must not be empty' if values.empty? raise LareRoundError, 'values must be an array' unless values.is_a? Array numbers_invalid = values.map { |i| i.is_a? Numeric } .reject { |i| i == true }.size if numbers_invalid.positive? error = <<-ERROR.strip.gsub(/\s+/, ' ') values contains not numeric values (#{numbers_invalid}) ERROR raise LareRoundError, error end unless values.map { |i| i.is_a? BigDecimal }.reject { |i| i == true }.empty? warning = <<-WARNING.strip.gsub(/\s+/, ' ') values contains non decimal values, you might loose precision or even get wrong rounding results WARNING warn warning end end
largest_remainder_method(mrc)
click to toggle source
# File lib/lare_round.rb, line 101 def largest_remainder_method(mrc) mrc.rounded_values = mrc.array_of_values.map do |v| largest_remainder_round(v, mrc) end until mrc.rounded_values.reduce(:+) >= mrc.rounded_total fractions = mrc.unrounded_values.zip(mrc.rounded_values).map do |x, y| x - y end mrc.rounded_values[fractions.index(fractions.max)] += 1 end mrc.rounded_values.map! { |v| v / mrc.decimal_shift } end
largest_remainder_round(value, mrc)
click to toggle source
# File lib/lare_round.rb, line 116 def largest_remainder_round(value, mrc) # items needed to be rounded down if positiv: # 0.7 + 0.7 + 0.7 = ( 2.1 ).round(0) = 2 # (0.7).round(0) + (0.7).round(0) + (0.7).round(0) = 1 + 1 + 1 = 3 # elsewise if negative rounding_strategy = if value.negative? BigDecimal::ROUND_UP else BigDecimal::ROUND_DOWN end value.round(mrc.precision, rounding_strategy) * mrc.decimal_shift end
process(values, precision)
click to toggle source
# File lib/lare_round.rb, line 23 def process(values, precision) if values.is_a? Hash process_hash(values, precision) else round_array_of_values(values, precision) end end
process_hash(values, precision)
click to toggle source
# File lib/lare_round.rb, line 31 def process_hash(values, precision) rounded_values = round_array_of_values(values.values, precision) values.tap do |hash| hash.keys.each_with_index do |key, index| hash[key] = rounded_values[index] end end end
round_array_of_values(array_of_values, precision)
click to toggle source
# File lib/lare_round.rb, line 85 def round_array_of_values(array_of_values, precision) mrc = Struct::IntermediaryResults.new mrc.precision = precision mrc.decimal_shift = BigDecimal(10**precision.to_i) mrc.rounded_total = array_of_values.reduce(:+) .round(precision) * mrc.decimal_shift mrc.array_of_values = array_of_values.map do |v| ((v.is_a? BigDecimal) ? v : BigDecimal(v.to_s)) end mrc.unrounded_values = array_of_values.map { |v| v * mrc.decimal_shift } largest_remainder_method(mrc) mrc.rounded_values end