Class: Dimensional::Metric
- Inherits:
-
Numeric
- Object
- Numeric
- Dimensional::Metric
- Defined in:
- lib/dimensional/metric.rb
Overview
A specific physical entity that can be measured. TODO: Add a hierarchy that allows metrics to be built into a taxonomy by domain, like shipping, carpentry or sports
Constant Summary collapse
- NUMERIC_REGEXP =
A Measure string is composed of a number followed by a unit separated by optional whitespace. A unit (optional) is composed of a non-digit character followed by zero or more word characters and terminated by some stuff. Scientific notation is not currently supported. TODO: Move this to a locale
/((?=\d|\.\d)\d*(?:\.\d*)?)\s*(\D.*?)?\s*(?=\d|$)/
Class Attribute Summary collapse
-
.base ⇒ Object
Returns the value of attribute base.
-
.default ⇒ Object
Returns the value of attribute default.
-
.dimension ⇒ Object
Returns the value of attribute dimension.
-
.universal_systems ⇒ Object
Returns the value of attribute universal_systems.
Instance Attribute Summary collapse
-
#unit ⇒ Object
readonly
Returns the value of attribute unit.
Class Method Summary collapse
-
.best_fit(target_oom, system) ⇒ Object
Sort units by “best” fit for the desired order of magnitude.
- .configuration ⇒ Object
- .configure(unit, options = {}) ⇒ Object
-
.find_unit(str, locale = Locale.default) ⇒ Object
Find the unit matching the given string, preferring units in the given locale.
-
.load(v) ⇒ Object
Create a new instance with the given value (assumed to be in the base unit) and convert it to the preferred unit.
-
.parse(str, locale = Locale.default) ⇒ Object
Parse a string into a Metric instance.
- .systems(locale) ⇒ Object
-
.units ⇒ Object
The units of this metric, grouped by system.
Instance Method Summary collapse
-
#base ⇒ Object
Return a new metric expressed in the base unit.
-
#change_system(system) ⇒ Object
Convert into the “most appropriate” unit in the given system.
-
#convert(new_unit) ⇒ Object
Convert this dimensional value to a different unit.
-
#initialize(value, unit = self.class.default || self.class.base) ⇒ Metric
constructor
A new instance of Metric.
- #inspect ⇒ Object
-
#localize(locale = Locale.default) ⇒ Object
(also: #preferred)
Convert into the best unit for the given Locale.
-
#strfmeasure(format) ⇒ Object
Like Date, Time and DateTime, Metric represents both a value and a context.
- #to_s ⇒ Object
Constructor Details
#initialize(value, unit = self.class.default || self.class.base) ⇒ Metric
Returns a new instance of Metric.
85 86 87 88 89 |
# File 'lib/dimensional/metric.rb', line 85 def initialize(value, unit = self.class.default || self.class.base) raise ArgumentError, "No default unit set" unless unit @unit = unit super(value) end |
Class Attribute Details
.base ⇒ Object
Returns the value of attribute base.
16 17 18 |
# File 'lib/dimensional/metric.rb', line 16 def base @base end |
.default ⇒ Object
Returns the value of attribute default.
16 17 18 |
# File 'lib/dimensional/metric.rb', line 16 def default @default end |
.dimension ⇒ Object
Returns the value of attribute dimension.
16 17 18 |
# File 'lib/dimensional/metric.rb', line 16 def dimension @dimension end |
.universal_systems ⇒ Object
Returns the value of attribute universal_systems.
16 17 18 |
# File 'lib/dimensional/metric.rb', line 16 def universal_systems @universal_systems end |
Instance Attribute Details
#unit ⇒ Object (readonly)
Returns the value of attribute unit.
84 85 86 |
# File 'lib/dimensional/metric.rb', line 84 def unit @unit end |
Class Method Details
.best_fit(target_oom, system) ⇒ Object
Sort units by “best” fit for the desired order of magnitude. Preference values offset OOM differences. There is a bias in favor of negative OOM differences (humans like 6“ more than 0.5ft).
67 68 69 70 71 72 73 74 75 |
# File 'lib/dimensional/metric.rb', line 67 def best_fit(target_oom, system) us = units[system] us = us.sort_by do |u| oom_delta = Math.log10(u.factor) - target_oom avoid_fraction_bonus = (oom_delta > 0.0 ? 0 : 1.5) (configuration[u][:preference] - oom_delta.abs) + avoid_fraction_bonus end us.last end |
.configuration ⇒ Object
33 34 35 36 37 |
# File 'lib/dimensional/metric.rb', line 33 def configuration @configuration ||= Hash.new do |h,u| h[u] = {:detector => u.detector, :format => u.format, :preference => u.preference} end end |
.configure(unit, options = {}) ⇒ Object
39 40 41 42 43 44 45 |
# File 'lib/dimensional/metric.rb', line 39 def configure(unit, = {}) @dimension ||= unit.dimension @base ||= unit @default ||= unit raise "Unit #{unit} is not compatible with dimension #{dimension || '<nil>'}." unless unit.dimension == dimension configuration[unit] = {:detector => unit.detector, :format => unit.format, :preference => unit.preference * 1.01}.merge() end |
.find_unit(str, locale = Locale.default) ⇒ Object
Find the unit matching the given string, preferring units in the given locale
28 29 30 31 |
# File 'lib/dimensional/metric.rb', line 28 def find_unit(str, locale = Locale.default) us = systems(locale).inject([]){|us, system| us + units[system].sort_by{|u| configuration[u][:preference]}.reverse} us.detect{|u| configuration[u][:detector].match(str.to_s)} end |
.load(v) ⇒ Object
Create a new instance with the given value (assumed to be in the base unit) and convert it to the preferred unit.
78 79 80 81 |
# File 'lib/dimensional/metric.rb', line 78 def load(v) raise "No base unit defined" unless base new(v, base).preferred end |
.parse(str, locale = Locale.default) ⇒ Object
Parse a string into a Metric instance. Providing a locale will help resolve ambiguities. Unrecognized strings return nil.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/dimensional/metric.rb', line 49 def parse(str, locale = Locale.default) elements = str.to_s.scan(NUMERIC_REGEXP).map do |(v, us)| unit = us.nil? ? default : find_unit(us, locale) raise ArgumentError, "Unit cannot be determined (#{us})" unless unit value = Integer(v) rescue Float(v) new(value, unit) end # Coalesce the elements into a single Measure instance in "expression base" units. # The expression base is the first provided unit in an expression like "1 mile 200 feet" elements.inject do |t, e| raise ArgumentError, "Inconsistent units in compound metric" unless t.unit.system == e.unit.system converted_value = e.convert(t.unit) new(t + converted_value, t.unit) end end |
.systems(locale) ⇒ Object
23 24 25 |
# File 'lib/dimensional/metric.rb', line 23 def systems(locale) locale.systems.dup.unshift(*(universal_systems || [])).uniq end |
.units ⇒ Object
The units of this metric, grouped by system.
19 20 21 |
# File 'lib/dimensional/metric.rb', line 19 def units @units ||= Unit.select{|u| u.dimension == dimension}.inject(Hash.new{|h, s| h[s] = []}){|h, u| h[u.system] << u;h} end |
Instance Method Details
#base ⇒ Object
Return a new metric expressed in the base unit
116 117 118 119 |
# File 'lib/dimensional/metric.rb', line 116 def base raise "No base unit defined" unless self.class.base convert(self.class.base) end |
#change_system(system) ⇒ Object
Convert into the “most appropriate” unit in the given system. A similar order-of-magnitude for the result is preferred.
98 99 100 101 102 103 |
# File 'lib/dimensional/metric.rb', line 98 def change_system(system) system = System[system] unless system.kind_of?(System) target_oom = Math.log10(self.unit.factor) bu = self.class.best_fit(target_oom, system) convert(bu) end |
#convert(new_unit) ⇒ Object
Convert this dimensional value to a different unit
92 93 94 95 |
# File 'lib/dimensional/metric.rb', line 92 def convert(new_unit) new_value = self * unit.convert(new_unit) self.class.new(new_value, new_unit) end |
#inspect ⇒ Object
149 150 151 |
# File 'lib/dimensional/metric.rb', line 149 def inspect strfmeasure("<%p <%#U>>") end |
#localize(locale = Locale.default) ⇒ Object Also known as: preferred
Convert into the best unit for the given Locale. The first system of the given locale with units is elected the preferred system, and within the preferred system, preference is given to units yielding a metric whose order of magnitude is close to zero.
107 108 109 110 111 112 |
# File 'lib/dimensional/metric.rb', line 107 def localize(locale = Locale.default) preferred_system = self.class.systems(locale).detect{ |s| self.class.units[s].any? } target_oom = Math.log10(self.abs) + Math.log10(self.unit.factor) rescue -(1.0/0.0) # Ruby 1.8.6 raises an exception on log10(0) instead of -Infinity bu = self.class.best_fit(target_oom, preferred_system) convert(bu) end |
#strfmeasure(format) ⇒ Object
Like Date, Time and DateTime, Metric represents both a value and a context. Like those built-in classes, Metric needs this output method to control the context. The format string is identical to that used by Kernel.sprintf with the addition of support for the U specifier:
%U replace with unit. This specifier supports the '#' flag to use the unit's name instead of abbreviation
In addition, this specifier supports the same width and precision modfiers as the '%s' specifier.
For example: %#10.10U
All other specifiers are applied to the numeric value of the measure. TODO: Support modulo subordinate units with format hash -> => “‘”, 12 => :inch or => “%d#”, 16 => “%doz.”
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/dimensional/metric.rb', line 133 def strfmeasure(format) v = if (precision = self.class.configuration[unit][:precision]) # TODO: This precision could more usefully be converted to "signifigant digits" pfactor = 10**(-precision) ((self * pfactor).round / pfactor.to_f) else __getobj__ # We need the native value to prevent infinite recursion if the user specifies the %s specifier. end format = format.gsub(/%(#)?([\d.\-\*]*)U/) do |s| us = ($1) ? unit.name : (unit.abbreviation || unit.name) Kernel.sprintf("%#{$2}s", us) end count = format.scan(/(?:\A|[^%])(%[^% ]*[A-Za-z])/).size Kernel.sprintf(format, *Array.new(count, v)) end |
#to_s ⇒ Object
121 122 123 |
# File 'lib/dimensional/metric.rb', line 121 def to_s strfmeasure(self.class.configuration[unit][:format]) end |