Better fractinoal display
This commit is contained in:
parent
bc65256776
commit
1150af5143
@ -1,9 +1,10 @@
|
|||||||
module UnitConversion
|
module UnitConversion
|
||||||
|
|
||||||
|
INTEGER_REGEX = /-?[0-9]+/
|
||||||
DECIMAL_REGEX = /(?:-?[0-9]+)?\.[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_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 = {
|
UNIT_ALIASES = {
|
||||||
cup: %w(cups c),
|
cup: %w(cups c),
|
||||||
@ -12,16 +13,60 @@ module UnitConversion
|
|||||||
ounce: %w(oz ounces),
|
ounce: %w(oz ounces),
|
||||||
pound: %w(pounds lb lbs),
|
pound: %w(pounds lb lbs),
|
||||||
pint: %w(pints),
|
pint: %w(pints),
|
||||||
quart: %w(quarts),
|
quart: %w(quarts qt),
|
||||||
gallon: %w(gallons ga),
|
gallon: %w(gallons ga),
|
||||||
|
|
||||||
gram: %w(grams g),
|
gram: %w(grams g),
|
||||||
kilogram: %w(kilograms kg),
|
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)]
|
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
|
class UnparseableUnitError < StandardError
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -78,7 +123,9 @@ module UnitConversion
|
|||||||
end
|
end
|
||||||
|
|
||||||
def get_value(str)
|
def get_value(str)
|
||||||
if str =~ /^#{RATIONAL_REGEX}$/
|
if str =~ /^#{INTEGER_REGEX}$/
|
||||||
|
str.to_i
|
||||||
|
elsif str =~ /^#{RATIONAL_REGEX}$/
|
||||||
parts = str.split(' ')
|
parts = str.split(' ')
|
||||||
if parts.length == 2
|
if parts.length == 2
|
||||||
whole = parts.first.to_r
|
whole = parts.first.to_r
|
||||||
@ -124,8 +171,12 @@ module UnitConversion
|
|||||||
converted = unit.convert_to(output_unit).value
|
converted = unit.convert_to(output_unit).value
|
||||||
end
|
end
|
||||||
|
|
||||||
if value.is_a? Rational
|
quantity_format(converted, value)
|
||||||
rational_val = converted.to_r.rationalize(0.01)
|
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
|
if rational_val.denominator == 1
|
||||||
rational_val.to_i.to_s
|
rational_val.to_i.to_s
|
||||||
elsif rational_val.denominator < rational_val.numerator.abs
|
elsif rational_val.denominator < rational_val.numerator.abs
|
||||||
@ -136,9 +187,68 @@ module UnitConversion
|
|||||||
rational_val.to_s
|
rational_val.to_s
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
"%g" % ("%.3f" % converted)
|
"%g" % ("%.3f" % quantity)
|
||||||
end
|
end
|
||||||
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
|
||||||
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 1/2')).to eq Rational(3, 2)
|
||||||
expect(UnitConversion.get_value('-1/2')).to eq Rational(-1, 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('-1 1/2')).to eq Rational(-3, 2)
|
||||||
|
expect(UnitConversion.get_value('18 9/10')).to eq Rational(189, 10)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns decimals' do
|
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('1', '1', 'tablespoon', 'cup')).to eq '1/16'
|
||||||
expect(UnitConversion.convert('2.0', '1', 'tablespoon', 'cup')).to eq '0.125'
|
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/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
|
end
|
||||||
|
|
||||||
it 'scales odd units without conversion' do
|
it 'scales odd units without conversion' do
|
||||||
|
Loading…
Reference in New Issue
Block a user