parsley/lib/unit_conversion/formatters.rb

75 lines
1.9 KiB
Ruby
Raw Normal View History

module UnitConversion
class NumberFormatter
def self.for(parsed_value, parsed_unit)
case
when parsed_unit && parsed_unit.metric?
DecimalFormatter
when parsed_value.rational?
RationalFormatter
else
DecimalFormatter
end.new
end
def initialize
end
# Converts a Numeric into a cooking-friendly Rational
def rationalize(value)
# NOTE: this algorithm seems stupid.
if value.floor == value
return value
end
useful_denominators = [2, 3, 4, 8, 16]
if value.is_a?(Rational) && useful_denominators.include?(value.denominator)
return value
end
whole = value.floor
fraction = value - whole
approximations = useful_denominators.map do |d|
best_n = (1...d).each.map { |n| {delta: (fraction - Rational(n, d)).abs, numerator: n} }.sort { |a, b| a[:delta] <=> b[:delta] }.first
{denominator: d, numerator: best_n[:numerator], delta: best_n[:delta]}
end
# Add 0 and 1
approximations << { denominator: 1, numerator: 0, delta: (fraction - Rational(0,1)).abs }
approximations << { denominator: 1, numerator: 1, delta: (fraction - Rational(1,1)).abs }
best = approximations.sort { |a, b| [a[:delta], a[:denominator]] <=> [b[:delta], b[:denominator]] }.first
Rational((whole * best[:denominator]) + best[:numerator], best[:denominator])
end
end
class RationalFormatter < NumberFormatter
def format(value)
rational_val = rationalize(value)
if rational_val.denominator == 1
rational_val.to_i.to_s
elsif rational_val.denominator < rational_val.numerator.abs
whole = rational_val.floor
fraction = rational_val - whole
"#{whole} #{fraction}"
else
rational_val.to_s
end
end
end
class DecimalFormatter < NumberFormatter
def format(value)
"%g" % ("%.3f" % value)
end
end
end