75 lines
1.9 KiB
Ruby
75 lines
1.9 KiB
Ruby
|
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
|