Better fractinoal display
This commit is contained in:
parent
bc65256776
commit
1150af5143
@ -1,9 +1,10 @@
|
||||
module UnitConversion
|
||||
|
||||
INTEGER_REGEX = /-?[0-9]+/
|
||||
DECIMAL_REGEX = /(?:-?[0-9]+)?\.[0-9]+/
|
||||
RATIONAL_REGEX = /-?(?:[0-9]+|(?:[0-9] )?[0-9]+\/[0-9]+)/
|
||||
RATIONAL_REGEX = /-?(?:[0-9]+ )?[0-9]+\/[0-9]+/
|
||||
UNIT_REGEX = /[\[\]a-zA-Z][\[\]a-zA-Z\/.\-()0-9]*/
|
||||
UNIT_PARSING_REGEX = /^(?<value>#{DECIMAL_REGEX}|#{RATIONAL_REGEX})\s+(?<unit>#{UNIT_REGEX})$/
|
||||
UNIT_PARSING_REGEX = /^(?<value>#{INTEGER_REGEX}|#{DECIMAL_REGEX}|#{RATIONAL_REGEX})\s+(?<unit>#{UNIT_REGEX})$/
|
||||
|
||||
UNIT_ALIASES = {
|
||||
cup: %w(cups c),
|
||||
@ -12,16 +13,60 @@ module UnitConversion
|
||||
ounce: %w(oz ounces),
|
||||
pound: %w(pounds lb lbs),
|
||||
pint: %w(pints),
|
||||
quart: %w(quarts),
|
||||
quart: %w(quarts qt),
|
||||
gallon: %w(gallons ga),
|
||||
|
||||
gram: %w(grams g),
|
||||
kilogram: %w(kilograms kg),
|
||||
milliliter: %w(ml milliliters)
|
||||
milliliter: %w(ml milliliters),
|
||||
liter: %w(l liters)
|
||||
}
|
||||
|
||||
UNIT_ALIAS_MAP = Hash[UNIT_ALIASES.map { |unit, values| values.map { |v| [v, unit] } }.flatten(1)]
|
||||
|
||||
# map that gives comfortable ranges for a given set of units
|
||||
UNIT_RANGES = {
|
||||
teaspoon: (0.125...3.0),
|
||||
tablespoon: (0.5...4.0),
|
||||
cup: (0.25...4.0),
|
||||
quart: (1.0...4.0),
|
||||
gallon: (0.5..9999),
|
||||
|
||||
ounce: (0..16),
|
||||
pound: (0.5..9999),
|
||||
|
||||
gram: (1.0..750),
|
||||
kilogram: (0.5..9999),
|
||||
|
||||
milliliter: (1.0..750),
|
||||
liter: (0.5..9999)
|
||||
}
|
||||
|
||||
UNIT_ORDERS = {
|
||||
standard_volume: [
|
||||
:teaspoon,
|
||||
:tablespoon,
|
||||
:cup,
|
||||
:quart,
|
||||
:gallon
|
||||
],
|
||||
|
||||
metric_volume: [
|
||||
:milliliter,
|
||||
:liter
|
||||
],
|
||||
|
||||
standard_mass: [
|
||||
:ounce,
|
||||
:pound
|
||||
],
|
||||
|
||||
metric_mass: [
|
||||
:gram,
|
||||
:kilogram
|
||||
]
|
||||
}
|
||||
|
||||
class UnparseableUnitError < StandardError
|
||||
end
|
||||
|
||||
@ -78,7 +123,9 @@ module UnitConversion
|
||||
end
|
||||
|
||||
def get_value(str)
|
||||
if str =~ /^#{RATIONAL_REGEX}$/
|
||||
if str =~ /^#{INTEGER_REGEX}$/
|
||||
str.to_i
|
||||
elsif str =~ /^#{RATIONAL_REGEX}$/
|
||||
parts = str.split(' ')
|
||||
if parts.length == 2
|
||||
whole = parts.first.to_r
|
||||
@ -124,8 +171,12 @@ module UnitConversion
|
||||
converted = unit.convert_to(output_unit).value
|
||||
end
|
||||
|
||||
if value.is_a? Rational
|
||||
rational_val = converted.to_r.rationalize(0.01)
|
||||
quantity_format(converted, value)
|
||||
end
|
||||
|
||||
def quantity_format(quantity, original_quantity)
|
||||
if original_quantity.is_a?(Rational) || original_quantity.is_a?(Integer)
|
||||
rational_val = rationalize(quantity)
|
||||
if rational_val.denominator == 1
|
||||
rational_val.to_i.to_s
|
||||
elsif rational_val.denominator < rational_val.numerator.abs
|
||||
@ -136,9 +187,68 @@ module UnitConversion
|
||||
rational_val.to_s
|
||||
end
|
||||
else
|
||||
"%g" % ("%.3f" % converted)
|
||||
"%g" % ("%.3f" % quantity)
|
||||
end
|
||||
end
|
||||
|
||||
def rationalize(value)
|
||||
|
||||
if value.is_a? Integer
|
||||
return value
|
||||
end
|
||||
|
||||
if value.floor == value
|
||||
return value
|
||||
end
|
||||
|
||||
useful_denominators = [2, 3, 4, 8, 16]
|
||||
|
||||
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
|
||||
|
||||
# Given an awkward measurement such as '18 9/10 oz' or '5/24 cup' or '0.09434 cup',
|
||||
# it will round the rational and scale the unit to give a more reasonable, useful measurement, ie
|
||||
# 19 oz, 3 Tbsp, 1 1/2 Tbsp
|
||||
def auto_unit(quantity, units)
|
||||
# First scale the unit if it seems to use the wrong unit
|
||||
normalized_unit = normalize_unit_names(units)
|
||||
value = initial_value = get_value(quantity)
|
||||
type_key, unit_orders = UNIT_ORDERS.detect { |key, orders| orders.include(normalized_unit.to_sym) }
|
||||
new_unit = normalized_unit
|
||||
|
||||
if unit_orders && (unit_range = UNIT_RANGE[normalized_unit.to_sym])
|
||||
|
||||
if value < unit_range.first
|
||||
unit_orders = unit_orders.reverse
|
||||
end
|
||||
|
||||
idx = unit_orders.index(new_unit.to_sym)
|
||||
|
||||
while !unit_range.include?(value) && idx < (unit_orders.length - 1)
|
||||
idx += 1
|
||||
next_unit = unit_orders[idx]
|
||||
value = Unitwise(value, new_unit).convert_to(next_unit).value
|
||||
new_unit = next_unit
|
||||
unit_range = UNIT_RANGE[new_unit.to_sym]
|
||||
end
|
||||
end
|
||||
|
||||
quantity_format(value, initial_value)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -8,6 +8,7 @@ RSpec.describe UnitConversion do
|
||||
expect(UnitConversion.get_value('1 1/2')).to eq Rational(3, 2)
|
||||
expect(UnitConversion.get_value('-1/2')).to eq Rational(-1, 2)
|
||||
expect(UnitConversion.get_value('-1 1/2')).to eq Rational(-3, 2)
|
||||
expect(UnitConversion.get_value('18 9/10')).to eq Rational(189, 10)
|
||||
end
|
||||
|
||||
it 'returns decimals' do
|
||||
@ -39,7 +40,7 @@ RSpec.describe UnitConversion do
|
||||
expect(UnitConversion.convert('1', '1', 'tablespoon', 'cup')).to eq '1/16'
|
||||
expect(UnitConversion.convert('2.0', '1', 'tablespoon', 'cup')).to eq '0.125'
|
||||
expect(UnitConversion.convert('2/3', '1', 'tablespoon', 'teaspoons')).to eq '2'
|
||||
expect(UnitConversion.convert('2', '4', 'teaspoons', 'tablespoons')).to eq '2 2 /3'
|
||||
expect(UnitConversion.convert('2', '4', 'teaspoons', 'tablespoons')).to eq '2 2/3'
|
||||
end
|
||||
|
||||
it 'scales odd units without conversion' do
|
||||
|
Loading…
Reference in New Issue
Block a user