The Units Pipe Dream

The Units Pipe Dream

Midnight to Three...

David J. Anderson
March 2008



Numbers are everywhere. They surround us in every direction we turn - the price of gas, the distance to the beach, the temperature outside, the weight we want to lose. Sometimes we mention the units - $3.29/gallon, 46 miles, 75 F, 15 lbs - but often they're understood intuitively, as if they were part of the local dialect. Though we may mention them in conversation, there's little penalty for leaving them unspoken: "three twenty-nine", "forty-six", "seventy-five", and "fifteen" are perfectly good conversational numbers. Because we're embedded in society and share common contexts, being explicit about units isn't strictly necessary.

It's only this common context that allows us to consider specifying units a formality. Things only get confusing when we switch contexts and converting between unit systems is necessary - 1.02 euros/liter, 74 kilometers, 24 C, 7 kgs. We stumble and fumble as we do our best to quickly compute estimates for these values in our heads until we've gotten a feel for the new units. When unit-intuition is lost, numbers are meaningless to us.

Whether or not they're mentioned, units always accompany numbers in the real world. Numbers by themselves are just a reckoning system - it's only when they're attached to units in context that they have meaning. Though mathematics has abstracted the concept of number and allowed us to count independently of units, the units aren't unimportant; they're still there lurking behing the scenes.

Programmers must work between the two perspectives depending on the kind of programs they're writing - either treating numbers as unitless, or being forced to consider units more formally. Because they aren't supported directly, in most situations units are an afterthought. Numeric values are calculated by programs and output onto screens, plugged into reports and spreadsheets - units are simply hardcoded on the screens and forms. When units are considered to be implicit in the problem domain, numbers can just be treated as integers, floats and doubles without unit. So units typically aren't carried through computations. They are a pain to deal with and they just slow programmers down, getting in their way for no good reason. Most programs today count and calculate without a real concern for units.

A cavalier attitude like this can cause serious problems, however. Incorrect conversion factors and forgotten scalings have led to the ruin of many projects - bridges have fallen down, planes have flipped over, buildings have toppled, boats have sunk. There needs to be some way to avoid this. It should be easy to associate units with numbers and convert them reliably within and between different systems of measurement. It's not a new idea, and many valiant attempts have been made.

Ruby and Units

The Ruby programming language does not have explicit support for units. Ruby developers will typically treat units as an afterthought in the same way as they would in other programming languages, converting unit-less numbers using conversion factors and simple transformations. However, there are a few frameworks for units that have been developed.

The three prevalent systems that are used to convert units are the hard-coded unit converters in Rails, the Units portion of the Facets Rubygem, and Carlson and Buttler's units Rubygem.

Unit Conversions in Rails

Rails ActiveSupport provides a simple unit conversion paradigm for several unit conversions, allowing you to say things like:

However, this is driven by hard code, not conversion data. And as such, it can fail if incorrectly:

Rails gives you a handy way to do conversions, but they have to be simple and there's nothing but numbers supporting them from underneath - they're not complete and aren't easily extensible. They do many of the things web developers need, though, and they come for free in today's most popular Ruby-based web development framework.

The Facets Rubygem

Since 2003, the Ruby Facets library has been growing, filled with new classes and extensions to existing classes. It's a truly phenomenal piece of work.

Part of this package is a units system that is quite full-featured.

A complete SI system is even available. The system promotes units as an object, and provides arithemtic methods to handle unit type compatability and conversion. The data files are formatted in YAML, and are extensive. However, it's missing a tie between the numbers and the units - the two are still separated conceptually.

The units Rubygem

In 2005, Carlson and Butler put together units.gem, which started down a different path, also loading their conversions from data and using method_missing to do conversions:

Very good stuff. But their definition strategy was not self referent, as the Facets system is; and their implementation did not promote units to first class objects.

It's All Good, but...

I also wanted to say some things like:

Basically units are a Domain Specific Language. They define things relative to each other, and the meaning of unit expressions is intuitive and flexible based on the types of the arguments. I wanted to define the units much like the way database migrations are defined in Rails, and allow forward reference to take place during definition, coding units and conversions in Ruby. I wanted the code readability to be at the same level as Ruby, using the method_missing to decompose messages and do the work, and optionally be able to reliably create methods - not have everything be pre-created. I also didn't want the package to be a heavyweight. I wanted it lean and mean and fast.

I felt that something comprehensive could be done and packed into a Rubygem that would give me a nice extensible framework for units in Ruby. So I started on a programming expedition...

The Pipe Dream - Part 1 - The Philosophy of Units

I began with some pondering. What are units, really? And how are they organized so that we can assume them without thinking about them?

Units are the quality of quantities that provide the context in which they can be compared to each other. This comparison is done in a measurement context - only values in the same context can be compared, such as by length, weight, or duration. A measurement context is expressed systematically, that is in the context of one or more system of units. Finally, the unit values themselves are defined within a system context.

Context is everything when it comes to our fundamental understanding of units. This taxonomy of Measures / Systems / Units provides what we need to put everything in perspective. For instance, we probably include the following subset:

Measure System Unit
Length English Yard
Foot
Inch
Metric Meter
Centimeter
Mass English Pound
Ounce
Metric Kilogram
Gram
Time Base Hour
Minute
Second

We can only compare values with the same Measure, we can convert values between Systems, and Units within those Systems can be sorted by scale. Contextually, knowing a Unit (either explicitly or implicitly) also means you know its Measure and System. It now also makes sense to convert between Units and Systems.

Interestingly, notice that we could have rooted the taxonomy with Systems instead of Measures. I chose Measure as more fundamental since it has its roots in discovery rather than invention, and Measure bounds the conversions we can do at the broadest level. In the chart, though Systems may occur in different Measures, they may not exist in all Measures which would leave us with gaps. The organization in the chart is not without it's own gaps (Time is labeled as Base, for example) but at least there are not gaps at the top level. And besides this, there is also a sense of cause and effect to this hierarchy: Measure came first by observing of the world around us, while systems were invented subsequent to the need to measure.

Also, we may have different names for Measures that mean the same thing formally, but have different informal contexts. For instance, Length and Distance are formally equivalent, but Length has a static connotation while Distance connotes movement. Time and Duration have are also equivalent but have different connotations (again, interestingly, static and dynamic.) The simple rule is that if you can use the same Units, the formal equivalence holds even though we may talk about the Measures differently.

Setting Ruby code to this, we have:

units.rb   a units pipe dream
        class Units
          @@measures = {}
        end

        class UnitsMeasure < MethodicHash
        end

        class UnitsSystem < MethodicHash
        end

        class UnitsUnit < MethodicHash
        end
        

The Units class is at the top level to provide a global context for defining and accessing the Units system, and managing a Hash of UnitsMeasures. UnitMeasures is a MethodicHash that contains UnitsSystems, and UnitSystems is a MethodicHash that contains UnitsUnits. A UnitsUnit is a MethodicHash that contains its properties.

Note the use of the MethodicHash. This class is just a Hash that defines its method_missing method to use its method argument as the value of the key used to store or retrieve its values. It's a convenient shorthand that makes accessing the hash look like accessing instance variables. The mechanism will be replicated at the class level in the Units class because it is not used to create instances, but only to root the sofware.

So, let's take the next step and detail the code skeleton.

The Units Skeleton

Units is used at the class level to manage a Hash of UnitsMeasure instances, so all of the methods are class methods.

units.rb   a units pipe dream
class Units

  def Units.units_measures
    @@measures.keys
  end

  def Units.create(name, &block)
    measure = (@@measures[name.to_s] ||= UnitsMeasure.new)
    block.call measure if block_given?
    measure
  end

  def Units.derive(name, target, &block)
    measure =
      (@@measures[name.to_s] = (target.kind_of? UnitsMeasure) ?
       target : Units.create(target.to_s))
    block.call measure if block_given?
    measure
  end

  def Units.delete(name)
    @@measures.delete name.to_s
  end

  def Units.clear
    @@measures.clear
    self
  end

  def Units.size
    @@measures.size
  end

  def Units.[](name)
    @@measures[name.to_s]
  end

  def Units.method_missing(method,*args)
    measure = self[method]
    raise UnitsException.new("UnitsMeasure '#{method}' undefined") if !measure
    measure
  end

  def Units.names_of(units_measure)
    @@measures.keys.select { |name| @@measures[name].equal? units_measure }
  end

end

The methods are all fairly simple and provide guarded access to the Hash. The units_measures method returns an Array of the names of the currently defined UnitsMeasures. The create method returns a new UnitsMeasure with the given, or the existing one with the given name if present. If a block is passed in, the UnitsMeasure is yielded to it. The derive method is much like create, associating a UnitsMeasure to the given name, except it is derived from existing UnitMeasures. If the target of the derivation is a UnitsMeasure, the name is associated with it. Otherwise a new UnitMeasure is created with the value of target as its name, and the name is associated with it. If a block is passed in, the UnitsMeasure is yielded to it. The delete method disassociates the name with a UnitsMeasure, and returns the name. The clear method empties the Hash of UnitMeasures and returns Units. The size method returns the number of UnitMeasures in the Hash. The [] method returns the UnitsMeasure with the given name from the Hash. The method_missing method tries to get the UnitsMeasure using the name of the method as the associated name, and returns it. If no value was found, a UnitsException is raised. The names_of method returns an Array of names that are associated with the given UnitsMeasure.

The key feature of the class is that it allows a UnitsMeasure to have multiple names associated with it, preserving formal equality while admitting connotative difference.

The UnitsMeasure Skeleton

A UnitsMeasure instance associates names to UnitsSystems.

units.rb   a units pipe dream
class UnitsMeasure

  def system(name,&block)
    system = (self[name] ||= UnitsSystem.new(self,name))
    block.call system if block_given?
    system
  end

  def names
    Units.names_of self
  end

end

There's not a lot to UnitsMeasure at this level, since most of the mechanism is managed by the MethodicHash from which it inherits. The system method creates a new UnitsSystem in the context of this UnitsMeasure or gets the existing one if found, and yields it to the block if it was passed in. The UnitsSystem is returned. The names method returns the names by which this UnitsMeasure is known at the top level.

The UnitsSystem Skeleton

A UnitsSystem instance associates names to UnitsUnits.

units.rb   a units pipe dream
class UnitsSystem

  attr_reader :units_measure, :name

  def initialize(units_measure,name)
    @units_measure = units_measure
    @name = name
  end

  def unit(attributes)
    unit = UnitsUnit.new self, attributes
    self[unit.name] = unit
  end

end

There's not a lot to the UnitsSystem class either, since most of the mechanism is managed by the MethodicHash from which it inherits. However, it does know to which UnitsMeasure it belongs. The initialize method sets up a new UnitsSystem, remembering its name and the given UnitsMeasure. The UnitsSystem is returned. The unit method associates a new UnitsUnit in the UnitsSystem with its name from the given Hash of options. The UnitsUnit is returned.

The UnitsUnit Skeleton

A UnitsUnit is a unit in a UnitsSystem, defined by its attributes.

units.rb   a units pipe dream
class UnitsUnit

  attr_reader :units_system

  def initialize(units_system,attributes)
    @units_system = units_system
    merge! attributes
  end

end

The UnitsUnit is trivial, containing only a simple initializer. The initialize method remembering the given UnitsSystem and merging the attributes into itself. The UnitsUnit is returned.

The UnitsException

Of course, when unexpected things happen related to Units, a UnitsException is raised.

units.rb   a units pipe dream
class UnitsException < Exception

end

This is just a cover for Exception in the Units context.

At this skeletal level, the classes are simple. The fun begins when they are fleshed out. Our next step is taking a closer look at Measures.

The Pipe Dream - Part 2 - Derived Measures

Measures come in two flavors: those that exist independently and stand up on their own, and those that are derived from other Measures. For instance, if length is an independent Measure, then area can be derived as length*length.

Since when you're working with measures the only thing that makes sense is multiplying them together, you only need to maintain three operations: multiplication, division and exponentiation. When units are derived, a combination of other units are combined to produce a new type of unit. For instance, consider kilograms divided by cubic meters. This formulation yields a density in kg/m3. But from the Measures point of view, you aren't interested in the kilograms and meters - it's mass and volume that are formulated to get density. The Density Measure is what is derived and is what drives the specific unit conversions.

Way back in an early mathematics class I was taught the concept of Dimension Analysis using what they called the fencepost method, used to figure out the units when factors are multiplied. The image is this: you have a series of fenceposts with a single rail between them. You line up the unit "dimensions" of a each value between the fenceposts such that the unit numerators are above the rail and the denomenators are below the rail. Then you run the rails, cancelling out any units that are both above and below. What's left are the units of the result. At the time it was a revelation, but of course it was only a simple crutch until a little later when negative exponents were introduced and it could all be done by addition and subtraction of exponents.

Measures Arithmetic

Only a few methods are needed to facilitate deriving Measures.

units.rb   a units pipe dream
class UnitsMeasure

  attr_reader :derived

  def **(exponent)
    UnitsMeasure.new.merge_derivation(self => exponent)
  end

  def /(divisor)
    UnitsMeasure.new.merge_derivation(self).merge_derivation(divisor,-1)
  end

  def *(factor)
    UnitsMeasure.new.merge_derivation(self).merge_derivation(factor)
  end

  def merge_derivation derivation, multiplier=1
    current = (self.derived ||= {})
    if derivation.kind_of? UnitsMeasure
      if derivation.derived
        derivation.derived.each {|key,value|
          current[key] = (current[key] || 0) + multiplier*value }
      else
        current[derivation] = (current[derivation] || 0) + multiplier
      end
    elsif derivation.kind_of? Hash
      derivation.each {|key,value|
        current[key] = ((current[key] || 0 ) + multiplier*value) }
    else
      raise UnitsException.new(
         "Cannot add #{derivation.self_name} to derivation")
    end
    self
  end

end

The Measures derivation is simple when viewed this way. The ** method creates a new UnitsMeasure and merges itself into the derivation raised to the given power. The / method creates a new UnitsMeasure, merges itself to the derivation, and merges the divisior to the derivation with its exponents inverted. The * method creates a new UnitsMeasure and merges itself and the factor to the derivation. For each method, a new UnitsMeasure is returned.

The merge_derivation method is the real workhorse. If the given derivation is a derived UnitsMeasure or a Hash, each element of the derivation is merged. If the given derivation is an underived UnitsMeasure, it is merged. Otherwise, we can't deal with it and a UnitsException is thrown.

The derivation is a Hash that is kept in the derived instance variable in the UnitsMeasure that associates UnitsMeasures to exponents. When the derivation is merged into a UnitsMeasure using merge_derivation, the exponents of existing elements are modified, or new elements are added if missing. What results is a new UnitsMeasure with the value of its derived set to the Measures that define it.

Attaching Derived Measures

Given the ability to create derived Measures, we must still be able to add them to the set of measures in the Units class.

This brings up another twist, which we handle in the same way we did for the UnitsMeasure creation. Namely, if two derived UnitsMeasures have the same derivation, we consider them to be formally equivalent. This makes sense: we consider all Measures to be unified. It goes right back to the previous statements about formalism and connotation. Two derived Measures can be formally equilvalent, but called by different names to preserve difference in connotation.

units.rb   a units pipe dream
class Units

  def Units.derive(name, target, &block)
    measure = ( @@measures[name.to_s] =
      if target.kind_of? UnitsMeasure
        if target.derived && (derived = find_by_derivation target.derived)
          derived
        else
          target
        end
      else
        Units.create(target.to_s)
      end )
    block.call(measure) if block_given?
    measure
  end

  def Units.find_by_derivation(derivation)
    matches = @@measures.values.uniq.select { |measure|
      measure.derived ? measure == derivation : false }
    case matches.size
      when 0 then nil
      when 1 then matches[0]
      else raise UnitsException.new(
          "Multiple UnitsMeasures with same derivation found")
    end
  end

end

The derive method is still the analogue of the create method, but it's been beefed up to handle derived UnitsMeasures. Given a name and target, if the target is a UnitsMeasure it checks to see if it is derived. If so, then it so then it sets it to a known UnitsMeasure with the same derivation, or adds it anew. If it isn't a UnitsMeasure, then name is a new connotation of the target, and the name is associated with the target UnitsMeasure. Note that create is called with target - which either finds its UnitsMeasure or creates a new one. Finally, the resultant UnitsMeasure is yielded to the block if it was given, and returned to the caller.

The find_by_derivation method finds a known UnitsMeasure matching the given derivation if it exists. Each of the known derived UnitsMeasures' derivations is tested for equality with the given derivation. If there are no matches, nil is returned. If one was matched, the match is returned. If more than one was found, then a unification problem occurred and it is time for us to raise a UnitsException.

We'll use the UnitsMeasures to do our conversions later. But before that, we need to detail the definition of Unit values.

The Pipe Dream - Part 3 - Unit Values

As people establish what sorts of things are going to be measured, Unit values are designated so that comparisons could be made. Whether it was sticks laid end to end measuring distance, rocks used in a balance to measure weight, or the position of the sun and moon in the sky to measure time, unit values provided a new sense of order to the world. We all could finally relate different objects to each other with common scales.

In human language there are three common constructs for speaking of units - by name, by the plural of the name, and by abbreviation. Since most of us capture the subtleties of this before we're grown, we hardly pay attention unless we encounter ambiguity or something we've not yet experienced. Fortunately for us, unit names, plurals and abbreviations are common and we have no problem with them.

The same cannot be said of computer software, of course. Computers have no real world experience to draw upon. We'll need to explicitly state the names, plurals and abbreviations we use to reference the units we define.

units.rb   a units pipe dream
class UnitsUnit

  def initialize(units_system,attributes)
    @units_system = units_system
    merge! attributes
    normalize
  end

  def normalize
    raise UnitsException.new("UnitUnits must have a name attribute") unless self.name
    self.name = self.name.to_s
    add_plural
    add_abbrevs
    self
  end

  def add_plural
    self.plural = (plural = self.plural) ? plural.to_s : self.name+'s'
  end

  def add_abbrevs(attribute = nil)
    if attribute == nil
      self.abbrevs = add_abbrevs(:abbrev)+add_abbrevs(:abbrevs)
    elsif (value = self[attribute])
      self.remove(attribute)
      (value.kind_of? Array) ? value.collect {|e| e.to_s } : [ value.to_s ]
    else
      [ ]
    end
  end

end

When a UnitsUnit is created, a short dance is done to normalize its name, plural and abbreviations. The initialize method is extended to call the normalize method after the incoming attributes have been merged into the instance and the instance is returned. The normalize method makes sure the name of the instance is a String and adds plurals and abbreviations to the instance. If no name is given as an attribute, a UnitsException is raised. The instance is returned. The add_plural method asigns the plural attribute and makes sure that it is a String if it exists. If it doesn't exist, it is created by adding an 's' to the value of the name.

The add_abbrevs method make sure that the abbrevs attribute is an Array of Strings. For flexibility, the values may be passed using the abbrev or abbrevs attribute, either as an Array or a single abbreviation. The method is intersting in that on entry from normalize, no arguments are given and the attribute is nil. This causes the abbrevs attribute to be set to the sum of the value of add_abbrevs called with the abbrev and the abbrevs attributes. When the method is entered with a non-nil attribute, the value is remembered, the attribute is removed and an Array is returned. If the value was an Array, it's elements are converted to Strings; if not an Array, then an Array is constructed from the value converted to a String. Finally if the value is nil, the attribute not being present, an empty Array is returned. And now, going back to the beginning, we can see the sum is a sum of Arrays, resulting in a new Array stored into the abbrevs attribute.

So now we have nice little mechanism that sets up named unit values with plurals and abbreviations. But to get at them, we need to build a quick-access mechanism.

units.rb   a units pipe dream
class UnitsSystem

  def unit(attributes)
    unit = UnitsUnit.new self, attributes
    self[unit.name] = unit
    Units.add_unit unit
  end

end


class Units

  @@unit_names = {}
  @@unit_plurals = {}
  @@unit_abbrevs = {}

  def Units.add_unit(unit)
    @@unit_names[unit.name] = unit
    @@unit_plurals[unit.plural] = unit
    unit.abbrevs.each { |abbrev| @@unit_abbrevs[abbrev] = unit }
  end

  def Units.clear
    @@measures.clear
    @@unit_names.clear
    @@unit_plurals.clear
    @@unit_abbrevs.clear
    self
  end

end

We create the high speed mechanism by rooting it at the top level, through Hashes of the unit's names, plurals and abbreviations. In UnitsSystems, the unit method is expanded to make a call to the add_unit method in Units. The Units.add_unit method associates the unit's name, unit's plural and each of the unit's abbreviations to the unit. The Units.clear method is extended to do a last spot of bookkeeping, emptying the three new Hashes.

Since it can look upwards for its UnitsSystem and UnitsMeasure, all of the relevant parts of the Units infrastructre can be retrieved once a UnitsUnit has been identified. Using these three hashes, identifying a UnitsUnit should very fast.

But what using these structures? There still isn't a way to equate units and look them up in context. That just happens to be our next step. But take care. It turns out this one is a doozy!

The Pipe Dream - Part 4 - Equating Units

When the relationship between units is being defined, we need to effectively say things like:

In the scheme we've set up so far, we make our unit definitions in code that reads a lot like the Rails DB migrations. We'll go ahead and add an equals attribute to our incoming unit Hash handling:

definitions.rb   a units pipe dream
    Units.create :length do |m|
      m.system :english do |s|
        s.unit :name => :inch,
               :plural => :inches,
               :abbrev => :in
        s.unit :name => :foot,
               :plural => :feet,
               :abbrev => :ft,
	       :equals => 12.inches
        s.unit :name => :yard,
               :abbrev => :yd,
	       :equals => 3.feet
      end
    end

Now we've done it - the can of worms is opened. Up to this point we've just been building interconnected data structures. But with this simple inclusion, we have a whole set of additional issues to contend with. How the heck can we make this work?

Let's consider the problem in the context of this simple inch-foot-yard example. The first thing to think about is that the inch unit has no equals attribute. Therefore, we'll consider it to be the basis for the Measure - all un-equaled units will be bases, and other Unit values will be relative to the bases. The next thing to notice is that the unit equality does not need to be defined in terms of the base. This is important since, for instance, people may define a mile as 5280 feet, but it is not likely they'll define it as 63360 inches. That is a constraint to which we do not want to be subject - we shouldn't have to define equality in terms of a base; computers should be figuring out all that for us. Of course, people may units in whatever way they wish - we don't want to preclude anyone from doing things in any way that makes sense to them.

So given this, how should we record the equality?

We'll focus on the equals attribute of the foot unit first. As given, the statement 12.inches will trigger a method_missing on the Fixnum object. Great - we'll just add a method_missing on Fixnum, do a conversion, attach the UnitsUnit, and away we go... Except, the Fixnum is a singleton... shared across all usage. And we want the same activity for other types of numbers. No, just adding units to a Numeric isn't quite good enough.

So we'll still put a method_missing on Numeric, but instead of converting the value and adding a UnitsUnit just, we'll create and return an instance of a new object, NumericWithUnits. Besides the advantage of not crufting up Numeric, we'll be able to add other methods to the new object that can handle conversions leaving us open to do all the crazy things we can dream up.

One might wonder why we're stoping with Numerics. Why not an ObjectWithUnits? This is a good question and we'll address it shortly. For now though, let's take a look at the consequences of our design choice and see how things shake out.

units.rb   a units pipe dream
class NumericWithUnits

  attr_reader :numeric, :unit

  def initialize(numeric,unit)
    @numeric, @unit = numeric, unit
  end

  def to_s
    "#{numeric} #{(numeric == 1)? unit.name : unit.plural}"
  end

end


class Units

  def Units.lookup(unit_identifier)
    unit_identifier = unit_identifier.to_s
    @@unit_names[unit_identifier] ||
    @@unit_plurals[unit_identifier] ||
    @@unit_abbrevs[unit_identifier] ||
    nil
  end

  def Units.convert(numeric,unit_identifier)
    unit = lookup(unit_identifier)
    raise UnitException.new("#{unit_identifier} missing") unless unit
    NumericWithUnits.new(numeric*unit.equals.numeric,unit.equals.unit)
  end

end


class UnitsUnit

  def normalize
    raise UnitsException.new("UnitUnits must have a name attribute") unless
      self.name
    self.name = self.name.to_s
    add_plural
    add_abbrevs
    add_equals
    self
  end

  def add_equals
    self.equals = NumericWithUnits.new(1,self) unless self.equals
  end

end


class Numeric

  alias :method_missing :method_missing_before_units

  def method_missing(method,*args)
    begin
      unit = Units.convert self, method
    rescue
      method_missing_before_units(method,args)
    end
  end

end

The changes required are not too complicated. The NumericWithUnits' initialize method assigns the incoming Numeric and UnitsUnit. The NumericWithUnits' to_s method returns a String containing its numeric part followed by its unit name or plural. The Units.lookup method returns the unit associated with a name, plural or abbreviation if it exists, or nil if it can't be found. The Units.convert method takes a numeric value and a unit name, plural or abbreviation and returns a new NumericWithUnits instance. The numeric part of the instance is converted relative to the identified unit, and the identified unit is assigned to the instance. If the name, plural or abbreviation could not be found, a UnitsException is raised. The UnitsUnit's normalize method is extended to call the add_equals method to add its equals attribute if missing. The UnitsUnit's add_equals method makes this unit a base - that is, if it has no equals attribute, it makes it a new NumericWithUnits instance with a quantity of 1 and a unit of itself. Finally, the Numeric's method_missing method is extended to try to convert to the given units if possible, or otherwise call the prior method_missing.

Great! Now we can load in units and have them all defined in our data structures in terms of base units. With these equalities in place, conversion is trivial!

But What About Context?

The Units.convert method does conversion quite nicely - but thinking ahead, we only want it to convert to the base units during definition! When we're not defining the units, we want to keep the units that are given:

What we need is a switch that only does the conversion based on whether or not we're in a defining context. We'll take that to mean: when calling the system method of a UnitsMeasure. Inside of these calls the system is in a defining context... but outside, is a using context. Note that the unit method of a UnitSystem is also a defining context, but units are not ever accessed within it except for in a defining context - nothing special is needed.

units.rb   a units pipe dream
class Units

  @@defining = nil

  def Units.defining?
    @@defining
  end

  def Units.defining(defining)
   @@defining = defining
  end

  def Units.convert(numeric,unit_identifier)
    unit = lookup(unit_identifier)
    raise UnitException.new("#{unit_identifier} missing") unless unit
    if defining?
      NumericWithUnits.new(numeric*unit.equals.numeric,unit.equals.unit)
    else
      NumericWithUnits.new(numeric,unit)
    end
  end

end


class UnitsMeasure

  def system(name,&block)
    Units.defining true
    system = (self[name] ||= UnitsSystem.new(self,name))
    block.call system if block_given?
    Units.defining nil
    system
  end

end

Definition declaration sort of crosscuts through this stuff; besides the creation of a defining class variable to hold the definition state in Units, additional changes must be made. The Units.defining? method returns true if a measure is being defined, indicating the defining context. The Units.defining method sets the measure being defined. The Units.convert method is extended to only convert to the base when in the defining context. Otherwise the initialization of the NumericWithUnits is done directly with the numeric and the unit that was found. The UnitsMeasure's system method is extended to indicate the defining context by setting and clearing itself.

By regulating all of the pathways into the system method, we ensure that the system will continue to work reasonably - below system, there's no place to establish a contextual foothold. If control goes through the method, we'll certainly get the context right unless another developer down the road starts changing our code. But since we're also writing unit tests as we go along here, I'm not too worred.

But we're not done yet.

What About Ambiguity?

In the last example, we used the unit "ounce". But did we mean a measure of volume, or were we perhaps really talking about weight? We don't know! It's ambiguous!

There are two ways to circumvent ambiguity The first is by avoiding it: simply work in a perfect world where it never appears. The second is to deal with it. Since we live in the real world, we must use or extend the context we've established to try to figure out something reasonable. But it may get a little hairy. Context is like that.

What ambiguity implies is choice. If there's just one thing, there's no ambiguity - it's only when more that one thing that can be chosen that ambiguity becomes an issue. When we're choosing from unit names, or plurals, or abbreviations, we've been expecting to see that we'll only get one unit back. But when we lookup ounces, we would potentially get more than one unit. We need to change out code a bit to turn this potential into reality.

This actually sheds some light on the the entire lookup process. Why do we need three Hashes when one would do? At the time, it seemed like a good idea to keep the name, plural and abbreviation different Hashes; but since we're just going to use them for lookup at the same time, let's reduce the code a bit while we're adjusting things.

Additionally, we need to take a closer look the defining state. While setting a true/false flag only conveys whether we're defining or not, we're going to need some more information to get us a further. We'll make a slight change to the mechanism to record the measure being defined - that way we can compare the unit being defined against it. If we encounter two units with the same name when defining, we'll prefer the one with the measure that matches the one we're defining. In fact, we'll also look at the derivation and if the requested unit is in a measure that's part of a derivation, we'll choose it over one that's not.

units.rb   a units pipe dream
class UnitsMeasure

  def system(name,&block)
    Units.defining self
    system = (self[name] ||= UnitsSystem.new(self,name))
    block.call system if block_given?
    Units.defining nil
    system
  end

end


class Units

  def Units.add_unit(unit,unit_identifier=nil)
    if unit_identifier
      if (element = @@units[unit_identifier])
        @@units[unit_identifier] += [ unit ] unless element.index(unit)
      else
        @@units[unit_identifier] = [ unit ]
      end
    else
      add_unit unit, unit.name
      add_unit unit, unit.plural
      unit.abbrevs.each { |abbrev| add_unit unit, abbrev }
    end
  end

  def Units.lookup(unit_identifier)
    @@units[unit_identifier.to_s] || [ ]
  end

  def Units.convert(numeric,unit_identifier)
    if (candidates = lookup(unit_identifier)).size == 0
      raise UnitsException.new("#{unit_identifier} missing")
    elsif !defining?
      if candidates.size > 1
        raise UnitsException.new("#{unit_identifier} ambiguous")
      else
        unit = candidates[0]
        NumericWithUnits.new(numeric,unit)
      end
    else
      units = candidates.select { |candidate|
        @@defining == candidate.units_system.units_measure }
      case units.size
        when 0 then
          raise UnitsException.new("#{unit_identifier} missing")
        when 1 then
          unit = units[0]
          NumericWithUnits.new(numeric*unit.equals.numeric,unit.equals.unit)
        else
          raise UnitsException.new("#{unit_identifier} ambiguous")
      end
    end
  end

  def Units.clear
    @@measures.clear
    @@units.clear
    self
  end

end


class UnitsUnit

  def ==(unit)
    self.equal?(unit) && self.units_system.equal?(unit.units_system)
  end

end


class NumericWithUnits

  def ==(value)
    (value.numeric == numeric) && (value.unit.equal? unit)
  end

end

The change to the indicate the defining state is slight. The change to handle the conversion in the defining versus using state is much more dramatic. The UnitMeasure's system method now sets the defining state to the measure of the unit being defined rather than just a true value. The Units.add_unit method is modified to add associations from the unit's name, plural and each abbreviation to the unit - but the associations are now to Arrays of units. Initially, we don't supply a unit_identifier. This triggers the code that adds the name, then the plural, then each abbreviation by re-calling the method. This time around a unit_identifier is supplied which adds the association to the Hash if not already present. We need this test-of-presence because if we redefine a unit, we need to make sure it only gets into the Array once.

The Units.convert method is significantly changed. First, if there are no units defined that match the unit_identifier, an exception is raised. If there are units, then if the system is in the defining state, then if a single unit matches, we return a new NumericWithUnits - otherwise, we have more than one matching unit and we have ambiguity. We'll figure out how to deal with using state ambiguity later; for now we'll just raise a UnitsException. If we're in the defining state, then we make sure the unit being used in the definition is in the right context. Once we've eliminated the units that don't match this tests, we should have gotten rid of our potential ambiguity. A single match indicates a non-ambiguous choice and we create a new NumericWithUnits; zero matches indicates a missing unit and more than one match means we still have ambiguity, so we throw a UnitException. The Units.clear now just clears out the one Hash that has replaced the three.

The UnitsUnit's == method is replaced and tests that the underlying MethodicHash is the same given unit. This is done so the index call on the Array in the Units.add_unit method will add the unit if it's in a different measure/system context - otherwise, we wouldn't add units with the same attributes. We also have to define NumericWithUnit's == method: equivalent numeric, the same unit.

Note that we do not go down to the system level for our ambiguity checks. Why not? Because we're just not going to find two different units in the same measure context with the same identifier. That would just be too unreasonable - while we aren't writing code for the perfect world, we have to assume everything is at least reasonable.

And What About Those Pesky Raises?

There are two types of UnitsExceptions we generated to deal with ambiguity: one for a missing unit - not found in the lookup, and the other for multiple units - too many found. While schematic problems could cause both errors, the first could also be a forward referencing issue - occuring when we are referencing a unit ahead of its definition. Happily, this problem can be overcome to a large extent by embedding a forward reference solution into the system.

The first thing we need to do is sort out our UnitsExceptions:

units.rb   a units pipe dream
class MissingUnitsException < UnitsException
end

class AmbiguousUnitsException < UnitsException
end

When we're defining units, if we get a MissingUnitsException we want to remember it and revisit it later when more information is present in the system - say, when more units have been defined. The rationale is this: if unit X is defined in terms of unit Y, but unit Y is encountered after unit X, then unit X will be missing unit Y and an exception will be raised. So, we remember the exception, keep accepting units, and once more units have been defined (possibly containing the definition of unit Y) we can go back and try unit X again. If unit Y has now been defined, the definition of unit X will succeed.

Yikes! How do we code such an animal? The happy answer is, we don't have to! I built a general-purpose forward referencing solution a while back that can do all the heavy lifting for us. To use this work, just a few minor changes and code insertions are needed:

units.rb   a units pipe dream
require 'forward_referencing'


class Numeric

  def method_missing(method,*args)
    if Units.defining?
      reference = Units.make_forward_reference(method,Units.defining?)
      begin
        value = Units.convert self, method
        Units.release_forward_reference reference
        value
      rescue MissingUnitsException
        Units.hold_forward_reference
      rescue UnitsException => exception
        units_problem("definition",exception,method,args)
      rescue Exception
        method_missing_before_units(method,args)
      end
    else
      begin
        Units.convert self, method
      rescue UnitsException => exception
        units_problem("usage",exception,method,args)
      rescue Exception
        method_missing_before_units(method,args)
      end
    end
  end

  def units_problem(state,exception,method,*args)
    raise exception
  end

end


class Units

  extend ForwardReferencing
  start_forward_referencing

  def Units.convert(numeric,unit_identifier)
    if (candidates = lookup(unit_identifier)).size == 0
      raise MissingUnitsException.new(unit_identifier.to_s)
    elsif !defining?
      if candidates.size > 1
        raise AmbiguousUnitsException.new(unit_identifier.to_s)
      else
        unit = candidates[0]
        NumericWithUnits.new(numeric,unit)
      end
    else
      units = candidates.select { |candidate|
        @@defining == candidate.units_system.units_measure }
      case units.size
        when 0 then
          raise MissingUnitsException.new(unit_identifier.to_s)
        when 1 then
          unit = units[0]
          NumericWithUnits.new(numeric*unit.equals.numeric,unit.equals.unit)
        else
          raise AmbiguousUnitsException.new(unit_identifier.to_s)
      end
    end
  end

  @@holding = nil

  def Units.hold_forward_reference(hold = true)
    @@holding = hold
  end

  def Units.holding_forward_reference?
    @@holding
  end

  def Units.make_forward_reference(method,context)
    @@holding ? nil : create_forward_reference(method,context)
  end

  def Units.release_forward_reference(reference = nil)
    remove_forward_reference(reference) if reference != nil
  end

  def Units.establish_forward_reference_context(context)
    Units.defining context
  end

  def Units.clear
    @@measures.clear
    @@units.clear
    forward_references_clear
    self
  end

end


class UnitsMeasure

  def system(name,&block)
    Units.defining self
    system = (self[name] ||= UnitsSystem.new(self,name))
    block.call system if block_given?
    Units.resolve_forward_references
    Units.defining nil
    system
  end

end


class UnitsSystem

  def unit(attributes)
    unit = nil
    if !Units.holding_forward_reference?
      unit = UnitsUnit.new self, attributes
      self[unit.name] = unit
      Units.add_unit unit
    else
      Units.hold_forward_reference nil
    end
    Units.continue_forward_reference_resolution
    unit
  end

end

The changes are hardly even invasive! We just require the forward_referencing feature, and away we go. Numeric's method_missing method is reorganized with respect to the defining context. If defining, we create a forward reference, try to convert, remove the forward reference if succesful and return the value. If the unit we needed was missing, we hold off on the unit definition. If some other Units-related problem occurred, we report it. Otherwise, we defer to the previous method_missing. If not defining, we try to convert and return the value if successul. If a Units-related problem occurred, we report it. Otherwise, we defer to the previous method_missing.

Numeric's units_problem method simply re-raises the UnitsException that occurred. This would likely be overriden for domain-based handling. Units is made to extend forward referencing, and starts it up. (The working details of the mechanism are described below.) The Units.convert method is adjusted to raise the MissingUnitsException and the AmbiguousUnitException that are rescued in Numeric's method_missing method.

The holding class variable is added to Units to indicate whether a definition should be added and associated or not based on the status of a forward reference. The Units.hold_forward_reference method sets the value of the variable, while the holding_forward_reference? method reports its value. What's important is that is we've gotten stopped during the definition, we don't want to add any other forward references until we get past it - otherwise we may save forward references that don't have a viable context for resolution later - that's why we have the make_forward_reference and release_forward_reference as holding-sensitive covers for the mixed-in create_forward_reference and remove_forward_reference methods. The Units.establish_forward_reference_context method asserts the given context as the defining context. UnitMeasure's system method adds a call to Units.resolve_forward_references when the system block is finished to try to resolve any unresolved forward references that may have occurred. UnitSystems's unit method is adjusted to be sensitive to forward reference holding, and adds a call to Units.continue_forward_reference_resolution to jump back into resolution when trying to resolve forward references.

What's happening is this: when we try to define a unit, the equals method depends on the resolution of the equality. The equality is in the form of number.unit_identifier. The unit_identifier is not a instance method in the Numeric class, so this fires the method_missing method. Since we're defining units, we create a forward reference with the expectation that there may be no unit associated with the method (that is, the unit_identifier) that was given. We then try to convert.

The Units.convert method looks up the given unit by identifier. If it couldn't be found, a MissingUnitsException is raised. Since we're defining, we qualify the units returned by the lookup against the measure being defined. If none of those match, then a MissingUnitsException is raised; if more than one was found, then an AmbiguousUnitsException is reased. If just one qualified, then a NumericWithUnits is created and returned.

Back in Numeric, if we didn't get a raise, we remove the forward reference because everything went fine, and we return the value. Now the unit method in UnitsSystem is called and the UnitsUnit is created and appropriately added. But if a MissingUnitsException occurred, Numeric places a hold on the system and proceeds. If some other type of UnitsException occurred (including an AmbiguousUnitsException) we're at a loss. We can't recover and we have to handle the problem outside of the system, so the units_problem method is called and the UnitsException is propagated.

The hold is interesting - the processing can't stop, and since the return type of calling hold is true, that's what gets returned from method_missing. It's not a Numeric, but that's okay - the unit method in UnitsSystem is called since we got through the Numeric with success. When the UnitsSystem's unit method is entered, it checks to see if we're holding - which we are. It doesn't create the unit - it just turns off the hold. Since we bypassed all of the effort to get the unit in, we want to turn off the hold so we can try the next unit definition. In either case, we call the continue_forward_reference_resolution method, which at this point is a no-op. We then return the unit that was created.

Note that in this flow, we never removed the forward reference. Units, which has been extended by ForwardReferencing is accumulating all of these forward references for us. We keep defining and accumulating until finally, the block that's doing the unit definitions returns in UnitsMeasure. Now we call the resolve_forward_references method - this is where the magic happens! Inside this method, we go back and try to resolve each forward reference, one at a time.

When we get ready to try, the context we established when we created the forward reference (remember - way back at the beginning of Numeric) is re-asserted through the establish_forward_reference_context method in Units. Following this, the resolver jumps back to the spot where we created the forward reference, as if we were trying to handle the equals method again! If the unit we need is still undefined, we go through the same mechanism we went through. But if the unit we are looking for has been defined (remember, we didn't stop - we kept going and the definition of the unit we needed may have since happenned) now Numeric will successfully get the converted value, the forward reference will be removed, no hold will be made, and the unit will be created in UnitSystem and properly associated. When we now hit the continue_forward_reference_resolution, it's no longer a no-op, but instead jumps back into the resolve_forward_references method and tries the next forward reference.

Once we've run through all of the forward references, if we did resolve something, we try to resolve again - the resolutions we made may have created the units other forward references depended on. But if nothing got resolved, we've done all we can and we return. If there are any unresolved forward references left over, we can try them again the next time units are created in the UnitsMeasure.

While this may seem complicated, all the dirty work is going on behind the scenes. All we really had to do was add calls to create and remove forward references, start and continue forward reference resolution, and implement a hold mechanism to keep a unit from being defined if it had a forward reference. Nice and clean.

Greek Style

One of the big benefits of the metric units system is the greek prefix. The prefix coming before a base unit multiplies it by a power of ten, making it easy for us to abstract scale away from our numbers. For instance, it's much easier for us to think in centimeters or kilometers than meters for certain scales, the way we would with inches or miles in the english system. However, the conversions are simple in metric (100 cm = 1 m, 1000m = 1 km, 100,000 cm = 1 km) while more difficult in english (12 in = 1 ft, 5280 ft = 1 mi, 63360 in = 1 mi).

Because the greek prefixes can be applied to any unit, we can make them easy to create:

units.rb   a units pipe dream
class UnitsSystem

  def unit(attributes)
    unit = nil
    if !Units.holding_forward_reference?
      unit = UnitsUnit.new self, attributes
      self[unit.name] = unit
      Units.add_unit unit
      case unit.greek
        when :ten then greek_ten unit
        when :two then greek_two unit
      end
    else
      Units.hold_forward_reference nil
    end
    Units.continue_forward_reference_resolution
    unit
  end

  def greek_ten(base)
    greek_unit base, "yocto", "y",  0.000000000000000000000001
    greek_unit base, "zepto", "z",  0.000000000000000000001
    greek_unit base, "atto",  "a",  0.000000000000000001
    greek_unit base, "femto", "f",  0.000000000000001
    greek_unit base, "pico",  "p",  0.000000000001
    greek_unit base, "nano",  "n",  0.000000001
    greek_unit base, "micro", "u",  0.000001
    greek_unit base, "milli", "m",  0.001
    greek_unit base, "centi", "c",  0.01
    greek_unit base, "deci",  "d",  0.1
    greek_unit base, "deca",  "da", 10.0
    greek_unit base, "hecto", "h",  100.0
    greek_unit base, "kilo",  "k",  1000.0
    greek_unit base, "mega",  "M",  1000000.0
    greek_unit base, "giga",  "G",  1000000000.0
    greek_unit base, "tera",  "T",  1000000000000.0
    greek_unit base, "peta",  "P",  1000000000000000.0
    greek_unit base, "exa",   "E",  1000000000000000000.0
    greek_unit base, "zetta", "Z",  1000000000000000000000.0
    greek_unit base, "yotta", "Y",  1000000000000000000000000.0
  end

  def greek_two(base)
    greek_unit base, "kilo",  "k", 2**10
    greek_unit base, "mega",  "m", 2**20
    greek_unit base, "giga",  "g", 2**30
    greek_unit base, "tera",  "t", 2**40
    greek_unit base, "peta",  "p", 2**50
    greek_unit base, "exa",   "e", 2**60
    greek_unit base, "zetta", "z", 2**70
    greek_unit base, "yotta", "y", 2**80
  end

  def greek_unit(base,prefix,abbrev_prefix,scalar)
    unit :name => prefix+base.name,
         :plural => prefix+base.plural,
         :abbrevs => base.abbrevs.collect { |abbrev| abbrev_prefix+abbrev },
         :equals => base.equals * scalar
  end

end

Hey - what's that greek_two method doing there? Well, there's also a common use of greek for powers of two because they're close to powers of ten (103 =~ 210). In fact with the advent of computer technology, the binary-based powers of two use of the prefixes is much a part of out lives as its decimal-based powers of ten precursor.

UnitSystem's unit method is extended to greek the unit if the greek attribute is set. UnitSystem's greek_ten method defines and creates the power-of-ten prefixes and scalar for a unit. UnitSystem's greek_two method defines and creates the power-of-two prefixes and scalar for a unit. UnitSystem's greek_unit method applies the given prefixes and scalar to a unit to create a new unit.

The reason that we define the powers of ten the way we do is so the roundoff in Ruby doesn't bite us. Take yocto and yotta for instance:

This all despite the fact that

A quick experiment

reveals that 23, 24, 25, 26, 28, 29, 32, 34, 39, 42, 45, 49, 50, 54, 56, 60, 63, 66, 72, 75, 81, 82, 84, 88, 91, 97 all have roundoff - that's over one quarter of just the first hundred powers of ten. A bug? Perhaps. But it is quite reproducible. We could delve deeper, but suffice to say that roundoff exists and expressing the number in greek_ten as the full sequence of digits tames the error.

The one thing that's still missing to make this work is scalar multiplication. In the greek_unit method, we have to be able to multiply a NumericWithUnits by a Numeric to get a new NumericWithUnits. While we're at it, we'll also throw in division. As we can see, when we multiply by a scalar, we're just multiplying the numeric part and leaving the units alone; we'll do the same with division.

units.rb   a units pipe dream
class NumericWithUnits

  def *(value)
    if value.kind_of? Numeric
      NumericWithUnits.new(numeric*value,unit)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def /(value)
    if value.kind_of? Numeric
      NumericWithUnits.new(numeric/value,unit)
    else
      raise UnitsException.new("units mismatch")
    end
  end

end

For both methods, if it's a Numeric we're applying, we create a new instance with its numeric part modified. At this time, we raise a UnitsException if the value isn't Numeric - we'll adjust this shortly.

Equating Units in Derived Measures

Okay! There's one more stop in this part of our journey: derived units! How do we represent units whose measures are derived?

Well, it just so happens that we have most of the pieces in place - we just need to use the derivation we tucked away in the UnitsMeasure to rationalize the process. But before we do, we need to make it a little easier on ourselves and do some refactoring.

Right now, our NumericWithUnits holds a Numeric and a UnitsUnit. Derivations are based on a collection of units that are raised to powers and multiplied together. We can do this in a Hash that is analogous to the work we did previously to accomodate derived measures. In this case the keys are UnitsUnit instances and the values are powers. Since this envelopes our previous representation as a Hash with a single element whose key is a UnitsUnit associated with a value of 1, there's no loss of functionality.

Because we know that we're tucking away UnitUnits and powers in this Hash, we'll specialize it to include some appropriate behaviors.

units.rb   a units pipe dream
class UnitsHash < Hash

  def initialize(unit = nil,power = 1)
    if unit
      if unit.kind_of? UnitsUnit
        self[unit] = power
      elsif unit.kind_of? UnitsHash
        unit.each { |k,v| self[k] = v*power }
      else
        raise UnitsException.new("invalid unit: #{unit}")
      end
    end
  end

  def to_s(numeric = 1,use_abbrevs = false)
    if size == 1 &&
        (su = select {|k,v| v == 1}).size == 1 &&
        !use_abbrevs
      su = su.to_a[0][0]
      (numeric == 1)? su.name : su.plural
    else
      abbrevs_to_s
    end
  end

  def abbrevs_to_s
    p = select {|k,v| v > 0}.sort.collect {|k,v|
      "#{k.abbrevs[0]}#{(v == 1) ? "" : "**#{v}"}"}
    n = select {|k,v| v < 0}.sort.collect {|k,v|
      "#{k.abbrevs[0]}#{(v == -1) ? "" : "**#{-v}"}"}
    numerator = (p.size > 0)? p.join(" ") : (n.size > 0)? "1" : ""
    denominator = (n.size > 0)? ((p.size > 0)? " / " : "/")+n.join(" ") : ""
    "#{numerator}#{denominator}"
  end

  def power!(power)
    self.each { |k,v| self[k] = v*power }
  end

  def **(power)
    clone.power!(power)
  end

  def merge!(value,power=1)
    value.unit.each { |k,v|
      self[k] = (self[k] || 0) + v*power
      delete k if self[k] == 0
    }
    self
  end

  def merge(value,power=1)
    clone.merge!(value,power)
  end

  alias :merge :*

end

We've captured the laws of exponents in here, as well as hijacked and expanded the code for printing unit names. The initialize method returns a new instance built from a UnitsUnit or another UnitsHash, optionally raised to a power. If no arguments are given, an empty instance is created. The to_s method returns a string representation of the instance. The caller can optionally provide a Numeric to have pluralization considered, and also indicate whether unit abbreviations will be used - but only in the case of a non-derived unit with a power of 1; otherwise, abbreviations will be used. The abbrevs_to_s method returns a string representation of the instance using abbreviations, formed as a numerator of units with positive powers over a denominator of units with positive powers. The power! method extends the power of each unit by the given power. The exponentiation operator returns a copy of the UnitsHash with the power of each unit extended by the given power - this is effectively unit exponentiation. The merge! method coallesces a given UnitsHash into the instance, adding in members that don't exist, extending members that do exist, and removing members whose powers become zero. Of course, we could keep the zero powers around, but since anything raised to the zeroth power is 1, we really don't need them anymore. The merge method coallesces a given UnitsHash into a copy of the instance. We alias the multiplication operation to this method - this is effectively unit multiplication.

By pushing the unit string representation code into UnitsHash we can simplify NumericWithUnits a little.

units.rb   a units pipe dream
class NumericWithUnits

  def initialize(numeric,unit)
    @numeric, @unit = numeric, units_hash(unit)
  end

  def units_hash(unit)
    (unit.kind_of? UnitsHash)? unit : UnitsHash.new(unit)
  end

  def to_s
    "#{numeric} #{unit.to_s(numeric)}"
  end

end

The initialize method sets the numeric as before, but calls the units_hash method to rationalize the unit argument. The units_hash method returns the argument if it is a UnitsHash, or otherwise creates a new UnitsHash built using it. The to_s method is simplified to return a string representation of the instance as the numeric and the String returned by the UnitHash's to_s method.

We now have everything set to create derived units. Amazingly enough, this simply amounts to extending and defining methods in NumericWithUnits.

units.rb   a units pipe dream
class NumericWithUnits

  def *(value)
    if value.kind_of? Numeric
      NumericWithUnits.new(numeric*value,unit)
    elsif value.kind_of? NumericWithUnits
      extend_units(value,1)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def /(value)
    if value.kind_of? Numeric
      NumericWithUnits.new(numeric/value,unit)
    elsif value.kind_of? NumericWithUnits
      extend_units(value,-1)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def **(value)
    if value.kind_of? Numeric
      extend_units(nil,value)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def align(target)
    factor = 1
    target_unit = UnitsHash.new
    target.unit.each do |tu,tv|
      su = target.unit.keys.select {|u| u.units_measure == tu.units_measure}
      if su.size == 1
        factor *= (tu.equals.numeric/su[0].equals.numeric)**tv
        target_unit[su[0]] = tv
      else
        target_unit[tu] = tv
      end
    end
    NumericWithUnits.new(numeric*factor,target_unit)
  end

  def extend(units,power)
    if !units
      NumericWithUnits.new(numeric**power,unit**power)
    else
      value = align(units)
      NumericWithUnits.new(value.numeric*(units.numeric**power),
                           value.unit.merge(units,power))
    end
  end

end

We're basically expanding the multiplication and division operators to handle NumericWithUnits, adding an exponetiation operator, and throwing in a few methods to do the real work. The multiplication operator extends the instance by multiplying by the given NumericWithUnits value with a power of 1. The division operator extends the instance by multiplying by the given NumericWithUnits value with a power of -1. The exponentiation operator extends the instance by raising the instance to the given power. Note that we only raise to numeric powers - raising to a value with units is just a bit too exotic. The align method returns a new NumericWithUnits that represents the target with it's units aligned to the instance. What this means is that for each of the instance's constituent units, a unit in the target with the same measure is promoted to that unit and it's numeric part is appropriately scaled. This effectively pulls all units in the same measure together. The extend method does double duty, either raising the instance to a power by rasing the numeric and unit part of the instance each to a power (if the units argument is nil) or multiplying the instance by the given numeric with units raised to the given power. The units are aligned prior to multiplication to make sure we can eliminate any units that end up with powers of 0.

So there we are. We can now define derived units appropriately. That covers all of our bases for unit definition and we'll move on to using units.

The Pipe Dream - Part 5 - Simply Units

Using numbers with units should be as simple as using numbers. They should do all of the same things as regular Ruby Numerics as transparently as possible.

Simple Output

Before we start using Units, we'll need a starting set of unit definitions to draw from. While I'm keen to drop in a whole load of definitions, I'll keep things simple here, and add definitions in later sections as needed while we move forward.

We'll use the length measure in the english system that we set up earlier. We already built inches, feet and yards so we'll just go with those as our base.

First we'll try a few simple identities:

This is just the sort of output we were looking for.

Note that I'll be using a representation here with Ruby code to the left of the arrow and output to the right in a schematized form (numbers are plain, hashes are surronded by curly braces, arrays by square brackets, string output by quotes, etc.) I'm just trying to be clear - if you know even a little Ruby, you'll get the idea.

Multiplication, Division and Exponentiation

Lets see if our multiplication, division and exponentiation do what they should too.

Interesting. While the second two statements seemed to work fine, the result was truncated in the first. Shouldn't we have gotten 7.2 inches? Well, yes and no. In the human world we expect the fraction, but Ruby is only doing what we tell it to do according to well-established rules. When we use integer arithmetic (FixNum instances in Ruby) we get truncation. To do what we want, we need to make our NumericWithUnits result use real numbers.

Fortunately, this is easy to do.

Take a look - we're consistent with Ruby arithmetic without units. This is precisely the kind of behavior we want! Let's try a few more things.

This makes sense - the result is the ratio between the two values - a unitless number. Also notice that here - where two NumericWithUnits are involved, we automatically get conversions to a real number. However, even though we can't see it, the value is a NumericWithUnits.

While this works - and it does need to continue to work - we probably should do a quick repair to NumericWithUnits's extend method and get back a Numeric in these cases.

units.rb   a units pipe dream
class NumericWithUnits

  def extend(units,power)
    if !units
      NumericWithUnits.new(numeric**power,unit**power)
    else
      value = align(units)
      extended_numeric = value.numeric*(units.numeric**power)
      extended_unit = value.unit.merge(units,power)
      (extended_unit.size == 0)? extended_numeric :
        NumericWithUnits.new(extended_numeric,extended_unit)
    end
  end

end

This is a route back to Numeric instances from NumericWithUnits intstances - a number without units really should be a Numeric. Appropriately, evaluating the expression now gives

Here's another thing:

The way things are set up, when multiplying (or dividing) the resulting units are aligned to those of the second value. The values are equalvalent, of course: 144 square inches is equal to 1 square foot since 12 inches is a foot. Take a look:

Huh? Wait a second. That's not right. We missed something - we have to align units so we aren't comparing apples and oranges. We need to clean up the == operator.

units.rb   a units pipe dream
class NumericWithUnits

  def ==(value)
    value = align(value)
    (value.numeric == numeric) && (value.unit.equal? unit)
  end

end

Ok. Let's try again.

That's better. So, like we said, 144 square inches is equal to 1 square foot.

What the heck? Let's take a closer look:

Damn. See what's happening? 144**2 = 20736. The NumericWithUnits 144.inches is being created prior to raising it to the second power - which squares both the numeric and unit parts. Friends, this is an ugly one. In fact, there's no good way to deal with it - it's one of those shorthands humans use all the time that defy the rules of mathematical logic. We could handle it by applying the ** operator to only the units part of the NumericWithUnits, but that doesn't work because we'd lose the ability to raise the whole instance to a power, and we don't want that to happen. What do we do?

Well, the first thing we'll do is make a longhanded version of our initializer.

units.rb   a units pipe dream
class NumericWithUnits

  def initialize(numeric,unit,power=1)
    @numeric, @unit = numeric, units_hash(unit)**power
  end

end

This will at least let us get the right result during creation if we're fastidious enough.

Unfortunately, this is impossibly painful for regular use. If we look a little more closely at our original problem, we can see that what we get is

instead of what we want

because of the way Ruby handles the operator precedence. In fact, there aren't any operators we can define that get evaluated ahead of the dereference ('dot') operator. We need to resort to extreme measures and do something depraved and shameful. We need to define a specialized unit-exponentiation operator.

units.rb   a units pipe dream
class NumericWithUnits

  def ^(value)
    if value.kind_of? Numeric
      NumericWithUnit.new(numeric,unit,value)
    else
      raise UnitsException.new("units mismatch")
    end
  end

end

Yeah, this is ugly - we're re-using the exclusive-or ('hat') operator as the power operator. But you know what? Tough. The exclusive-or is a logical, set-theoretical operator - it only has a loose interpretation in the numeric domain. In fact, we'll adopt a convention for units - we'll make them always use the 'hat' notation for unit powers.

units.rb   a units pipe dream
class UnitsHash

  def abbrevs_to_s
    p = select {|k,v| v > 0}.sort.collect {|k,v|
      "#{k.abbrevs[0]}#{(v == 1) ? "" : "^#{v}"}"}
    n = select {|k,v| v < 0}.sort.collect {|k,v|
      "#{k.abbrevs[0]}#{(v == -1) ? "" : "^#{-v}"}"}
    numerator = (p.size > 0)? p.join(" ") : (n.size > 0)? "1" : ""
    denominator = (n.size > 0)? ((p.size > 0)? " / " : "/")+n.join(" ") : ""
    "#{numerator}#{denominator}"
  end

end

Finally, we have something that we can do the job for us.

Note that we didn't replace exponentiation - we still need it! We just have a different symbol to use to just exponentiate units. The biggest problem we have by doing this is that the operator precedence gets messed up - the 'hat' has a precedence below the rest of arithmetic. This is really ugly - it means that something like

that would be a perfectly good expression to use to extrude a volume from an area would be evaluated as

since multiplication has higher precedence than exclusive-or. Unfortunately, because the precedence of Ruby operators is not configurable, there is no way to recover from this one without parenthesis. We need to do it like

using parenthesis to enforce the correct evaluation order. Since many of the unit uses will be defined as variable expressions

this shouldn't be a problem most of the time. But in all honesty, I feel a little dirty after doing this.

There is another way to look at the problem that we need to consider:

This is a valid expression that unfortunately wont't work. Why? Because, when the * operator gets called, it's getting called on the Numeric, not the NumericWithUnits. Let's go fix that up.

units.rb   a units pipe dream
class NumericWithUnits

  def NumericWithUnits.create_commutative_operators(klass)
code = <<operatorsEND
  alias_method :old_multiply, :*

  def *(value)
    (value.kind_of? NumericWithUnits) ? value*self : old_multiply(value)
  end

  alias :old_divide :/

  def /(value)
    (value.kind_of? NumericWithUnits) ? (value**-1)*self : old_divide(value)
  end
operatorsEND
    klass.class_eval code
  end

end


NumericWithUnits.create_commutative_operators Fixnum
NumericWithUnits.create_commutative_operators Bignum
NumericWithUnits.create_commutative_operators Float

Here we're using a little bit of metaprogramming - we've written some code that writes code, and when evaluated adds method to a class. What the code does is modifies the computation when the argument is a NumericWithUnits, appropriately reversing the order of the operation; otherwise, everything is left alone. Since multiplication (and by using the reciprocal, division) is commutative, the result is independent of the order of the values. We create the operations for Ruby's fixed, big fixed and real number classes.

Sweet. It looks like everything's going fine! Multiplication and division work great, but we'll need parenthesis a little more often, when we use the 'hat' operation in an out-of-precedence position.

Addition and Subtraction

You might have thought we should have done addition before multiplication, but not so. The twist in addition (and subtraction) is that you can't add two numbers with units unless they have the same units - the result doesn't make sense mathematically. What does 3.feet + 2.seconds mean? You just don't get a reasonable answer. Sure, we might be able to come up with something meaningful, but it'd be a stretch. We want to keep things making sense or this effort will be for naught.

Fortunately, we built most of the mechanicism we need to do this when we were working on multiplication. We define addition and subtraction and modify our align method to do just what we need. First we'll adjust the align method.

units.rb   a units pipe dream
class NumericWithUnits

  def align(target,all=true)
    factor = 1
    target_unit = UnitsHash.new
    target.unit.each do |tu,tv|
      su = target.unit.keys.select {|u| u.units_measure.equal? tu.units_measure }
      if su.size == 1
        factor *= ((1.0*tu.equals.numeric)/su[0].equals.numeric)**tv
        target_unit[su[0]] = tv
      else
        target_unit[tu] = tv
      end
    end
    raise UnitsException.new("units mismatch") if all && (unit != target_unit)
    NumericWithUnits.new(numeric*factor,target_unit)
  end

end

We'll add an argument to the method: all will indicate that the resulting units of the target must equal the instance's units. The code is the same except for the inclusion of raising a UnitsException if this condition is false . While we're at it, we'll also make sure we don't lose fractional parts when the numeric parts coming in are integers.

Now we can add the addition and subtraction operations, but we have to adjust our call to align in the extend method. We'll also throw in a unary plus and minus for compatability and to let us implement subtraction as addition.

units.rb   a units pipe dream
class NumericWithUnits

  def +@
    clone
  end

  def -@
    value = clone
    value.numeric = -(value.numeric)
    value
  end

  def +(value)
    if value.kind_of? NumericWithUnits
      aligned_value = align(value)
      aligned_value.numeric += value.numeric
      aligned_value
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def -(value)
    self + (-value)
  end

  def extend(units,power)
    if !units
      NumericWithUnits.new(numeric**power,unit**power)
    else
      value = align(units,false)
      extended_numeric = value.numeric*(units.numeric**power)
      extended_unit = value.unit.merge(units,power)
      (extended_unit.size == 0)? extended_numeric :
        NumericWithUnits.new(extended_numeric,extended_unit)
    end
  end

end

Now when we try to add one NumericWithUnits to another, the units are aligned prior to performing the addition, to make sure the numeric part we're adding to has been scaled appropriately. If the units don't match, the align method throws a UnitsException.

Continuing on this path, does it make any sense to add a unitless value to a value with units? Technically no, but practically yes. In the real world, "5 inches plus 2" is 7 inches; the 2 is assumed to be in inches because people are smart and can make that connection. Of course programmers are smart people too and we want to make their lives easier, so we'll also mimic the human interpretation in our Ruby code. We just need a slight adustment in our add method.

units.rb   a units pipe dream
class NumericWithUnits

  def +(value)
    if value.kind_of? Numeric
      NumericWithUnits.new(numeric+value,unit)
    elsif value.kind_of? NumericWithUnits
      aligned_value = align(value)
      aligned_value.numeric += value.numeric
      aligned_value
    else
      raise UnitsException.new("units mismatch")
    end
  end

end

This gives us our desired behavior.

Of course, we should also make sure to add the commutative operations

units.rb   a units pipe dream
class NumericWithUnits

  def NumericWithUnits.create_commutative_operators(klass)
code = <<operatorsEND
  alias_method :old_multiply, :*

  def *(value)
    (value.kind_of? NumericWithUnits) ? value*self : old_multiply(value)
  end

  alias :old_divide :/

  def /(value)
    (value.kind_of? NumericWithUnits) ? (value**-1)*self : old_divide(value)
  end

  alias_method :old_add, :+

  def +(value)
    (value.kind_of? NumericWithUnits) ? value+self : old_add(value)
  end

  alias :old_subtract :-

  def -(value)
    (value.kind_of? NumericWithUnits) ? (-value)+self : old_subtract(value)
  end
operatorsEND
    klass.class_eval code
  end

end

So that

works too, which despite being a little more tenuous in human usage should really be there for mathematical completeness. Otherwise, of course, someone will try it, get an error, and wonder why.

Comparison

A NumericWithUnits should, like its Numeric breathren, be comparable. We should be able to determine whether one is less than, greater than, less than or equal to, greater than or equal to, equal to or not equal to another. Ruby has a nice clean shorthand for this, the compare operator and companion Comparable module.

What we need to do is define the <=> operator and mix in Comparable. The really neat thing is, since Comparable gives us the == method, we can remove it from the class and leverage the one that's provided by the module.

units.rb   a units pipe dream
class NumericWithUnits

  understands Comparable

  def <=>(value)
    if value.kind_of? Numeric
      numeric <=> value
    elsif value.kind_of? NumericWithUnits
      align(value).numeric <=> value.numeric
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def NumericWithUnits.create_commutative_operators(klass)
code = <<operatorsEND
  alias_method :old_multiply, :*

  def *(value)
    (value.kind_of? NumericWithUnits) ? value*self : old_multiply(value)
  end

  alias :old_divide :/

  def /(value)
    (value.kind_of? NumericWithUnits) ? (value**-1)*self : old_divide(value)
  end

  alias_method :old_add, :+

  def +(value)
    (value.kind_of? NumericWithUnits) ? value+self : old_add(value)
  end

  alias :old_subtract :-

  def -(value)
    (value.kind_of? NumericWithUnits) ? (-value)+self : old_subtract(value)
  end

  alias :old_compare :<=>

  def <=>(value)
    (value.kind_of? NumericWithUnits) ? (value <=> self) : old_compare(value)
  end

  alias :old_gt :>

  def >(value)
    (value.kind_of? NumericWithUnits) ? (value < self) : old_gt(value)
  end

  alias :old_lt :<

  def <(value)
    (value.kind_of? NumericWithUnits) ? (value > self) : old_lt(value)
  end

  alias :old_gteq :>=

  def >=(value)
    (value.kind_of? NumericWithUnits) ? (value <= self) : old_gteq(value)
  end

  alias :old_lteq :<=

  def <=(value)
    (value.kind_of? NumericWithUnits) ? (value >= self) : old_lteq(value)
  end

  alias :old_eq :==

  def ==(value)
    (value.kind_of? NumericWithUnits) ? (value == self) : old_eq(value)
  end
operatorsEND
    klass.class_eval code
  end

end

Notice that we included the unitless variants as well and expanded the commutative operations to include comparison. The question is why did we need to include the >, <, >=, <= and == in the commutative operations instead of just <=>? The answer is that for Fixnum, Bignum and Float instances, the individual operations are defined separately for performance reasons. We must override them to alter the behavior for NumericWithUnits instances - changing <=> isn't enough.

Voila! We now implement comparison, with all the appropriate variants. You can try the unitless variants yourself if you'd like; the results are the same.

Since the code for that section is really smelling funny because it's geting so long, we'll do a quick refactoring.

units.rb   a units pipe dream
class NumericWithUnits

  def NumericWithUnits.commutative_operator(op,old,calc)
    ([] <<
      "alias :old_#{old} :#{op}" <<
      "def #{op}(value)" <<
      "  (value.kind_of? NumericWithUnits) ? #{calc} : old_#{old}(value)" <<
      "end").
    join("\r\n")
  end

  def NumericWithUnits.create_commutative_operators(klasses)
    commutative_operators =
      ([] <<
         commutative_operator( "*",   "multiply", "value * self"       ) <<
         commutative_operator( "/",   "divide",   "(value**-1) * self" ) <<
         commutative_operator( "+",   "add",      "value + self"       ) <<
         commutative_operator( "-",   "subtract", "(-value) + self"    ) <<
         commutative_operator( "<=>", "compare",  "-(value <=> self)"  ) <<
         commutative_operator( ">",   "gt",       "(value < self)"     ) <<
         commutative_operator( "<",   "lt",       "(value > self)"     ) <<
         commutative_operator( ">=",  "gteq",     "(value <= self)"    ) <<
         commutative_operator( "<=",  "lteq",     "(value >= self)"    ) <<
         commutative_operator( "==",  "eq",       "(value == self)"    )).
      join("\r\n")
    klasses.each { |klass| klass.class_eval commutative_operators }
  end

end


NumericWithUnits.create_commutative_operators [ Fixnum, Bignum, Float ]

That's much cleaner now.

Modulo

There's one other mathematical operator we need to include: the modulo operation, which returns the remainder of a division. This one isn't too bad, but it's too bizarre a calculation to do simply in commutative form - so we'll use a helper.

units.rb   a units pipe dream
class NumericWithUnits

  def %(value)
    if value.kind_of? Numeric
      NumericWithUnits.new(numeric % value,unit)
    elsif value.kind_of? NumericWithUnits
      aligned_value = align(value)
      aligned_value.numeric = aligned_value.numeric % value.numeric
      aligned_value
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def inv_mod(value)
    if value.kind_of? Numeric
      NumericWithUnits.new(value % numeric,unit)
    elsif value.kind_of? NumericWithUnits
      aligned_value = align(value)
      aligned_value.numeric = value.numeric % aligned_value.numeric
      aligned_value
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def NumericWithUnits.create_commutative_operators(klasses)
    commutative_operators =
      ([] <<
         commutative_operator( "*",   "multiply", "value * self"        ) <<
         commutative_operator( "/",   "divide",   "(value**-1) * self"  ) <<
         commutative_operator( "+",   "add",      "value + self"        ) <<
         commutative_operator( "-",   "subtract", "(-value) + self"     ) <<
         commutative_operator( "%",   "modulo",   "value.inv_mod(self)" ) <<
         commutative_operator( "<=>", "compare",  "-(value <=> self)"   ) <<
         commutative_operator( ">",   "gt",       "(value < self)"      ) <<
         commutative_operator( "<",   "lt",       "(value > self)"      ) <<
         commutative_operator( ">=",  "gteq",     "(value <= self)"     ) <<
         commutative_operator( "<=",  "lteq",     "(value >= self)"     ) <<
         commutative_operator( "==",  "eq",       "(value == self)"     )).
      join("\r\n")
    klasses.each { |klass| klass.class_eval commutative_operators }
  end

end

This gives us

which is just what we expected.

Approximate Equality

A comparison operation that doesn't come as part of the Ruby core, but one that I've personally found to be extremely useful is comparing to see if two numbers are approximately equal to each other. Approximate equality is tested with the approximately_equals? method and the =~ operator that are defined in my eymiha_math rubygem.

The problem occurs when we consider two values that are close to each other, but not exact. Consider the length of the diagonal of a two-foot square:

Yeah, right. The two values are equal for all practical purposes. The difference is miniscule.

The idea behind approximate equality is that if the distance between two values is less than some "measurement error" threshhold (called epsilon in Mathematics) then for all practical purposes the two values are equal. If we download the eymiha_math gem and require 'approximately_equals' in our code, we can check the relationship with Numerics. This can be easily extended to NumericWithUnits.

units.rb   a units pipe dream
require 'approximately_equals'


class NumericWithUnits

  def approximately_equals?(value,epsilon=Numeric.epsilon)
    if value.kind_of? Numeric
      numeric.approximately_equals?(value,epsilon)
    elsif value.kind_of? NumericWithUnits
      align(value).numeric.approximately_equals?(value.numeric,epsilon)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  alias :=~ :approximately_equals?

  def NumericWithUnits.create_commutative_operators(klasses)
    commutative_operators ||=
      ([] <<
         commutative_operator( "*",   "multiply", "value * self"        ) <<
         commutative_operator( "/",   "divide",   "(value**-1) * self"  ) <<
         commutative_operator( "+",   "add",      "value + self"        ) <<
         commutative_operator( "-",   "subtract", "(-value) + self"     ) <<
         commutative_operator( "%",   "modulo",   "value.inv_mod(self)" ) <<
         commutative_operator( "<=>", "compare",  "-(value <=> self)"   ) <<
         commutative_operator( ">",   "gt",       "(value < self)"      ) <<
         commutative_operator( "<",   "lt",       "(value > self)"      ) <<
         commutative_operator( ">=",  "gteq",     "(value <= self)"     ) <<
         commutative_operator( "<=",  "lteq",     "(value >= self)"     ) <<
         commutative_operator( "==",  "eq",       "(value == self)"     ) <<
         commutative_operator( "=~",  "approxeq", "(value =~ self)"     )).
      join("\r\n")
    klasses.each { |klass| klass.class_eval commutative_operators }
  end

end

The operation works just like equality, with a little fudge thrown in:

The epsilon value can be changed to adjust the approximate equality threshhold.

Other Niceties

While addition, subtraction, multiplication, division, exponentiation, modulo and comparison are all core mathematical operations for dealing with units, there are some more methods we need if we'd like them to act like regular Numeric types.

Happily we don't have to write them. Instead we'll get them by piggybacking on the numeric part of a NumericWithUnits, forwarding the call using method_missing.

units.rb   a units pipe dream
class NumericWithUnits

  def method_missing(method,*args)
    NumericWithUnits.new(numeric.send(method,*args),unit)
  end

end

Using a smattering of calls we know are in Numeric,

We're going to use the method_missing method a lot more, but we'll keep this as the last step to make sure we fall through to the underlying Numeric's methods when we don't recognize the intended method.

Bypassing Type Checks

In our code we frequently need to check to see if we have the right kind of object before execute some code; we use the kind_of? method to do this. This discrimination violates the dynamic spirit of Ruby in a way - but it's needed because objects don't all behave the same way and we sometime really need to know what we're dealing with before we do something. Ruby advocates "duck typing" in which methods are called on objects and any fallout that happens is handled by the receiver. The terminology comes from the old expression, "if it walks like a duck, and quacks like a duck, it must be a duck."

In our case, if an object can respond to a method, it must be something that could handle the method. Doing this gives the receiver the opportunity to figure out how to perform the function is it's so inclined, either by calling other methods, delegating the call to another object, or creating new code to do the work. We want NumericWithUnits to act like a Numeric, and we've done that. For all practical purposes, NumericWithUnits is a Numeric duck.

The problem is that NumericWithUnits does not inherit from Numeric. This means that if some code interrogates an instance of NumericWithUnits to see if its a Numeric

the value returned would be false. We'd like this to be true despite the inheritence. Fortunately, we can.

units.rb   a units pipe dream
class NumericWithUnits

  alias :old_kind_of? :kind_of?

  def kind_of?(klass)
    (numeric.kind_of? klass)? true : old_kind_of?(klass)
  end

end

We redirect the request to the numeric because that's what makes the instance compatible with the Numeric. So now,

You have to love Ruby. Faking out the inheritence tree is just too darned cool.

Of course, this means that we need to rearrange some of our methods - whenever we discriminate between a NumericWithUnits and a Numeric, we need to check for the NumericWithUnits first since both of the kind_of? tests on a NumeriWithUnits will now return true.

units.rb   a units pipe dream
class NumericWithUnits

  def <=>(value)
    if value.kind_of? NumericWithUnits
      align(value).numeric <=> value.numeric
    elsif value.kind_of? Numeric
      numeric <=> value
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def approximately_equals?(value,epsilon=Numeric.epsilon)
    if value.kind_of? NumericWithUnits
      align(value).numericapproximately_equals?(value.numeric,epsilon)
    elsif value.kind_of? Numeric
      numeric.approximately_equals?(value,epsilon)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def +(value)
    if value.kind_of? NumericWithUnits
      aligned_value = align(value)
      aligned_value.numeric += value.numeric
      aligned_value
    elsif value.kind_of? Numeric
      NumericWithUnits.new(numeric+value,unit)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def *(value)
    if value.kind_of? NumericWithUnits
      extend(value,1)
    elsif value.kind_of? Numeric
      NumericWithUnits.new(numeric*value,unit)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def /(value)
    if value.kind_of? NumericWithUnits
      extend(value,-1)
    elsif value.kind_of? Numeric
      NumericWithUnits.new(numeric/value,unit)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def **(value)
    if value.kind_of? Numeric && !(value.kind_of? NumericWithUnits)
      extend(nil,value)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def ^(value)
    if value.kind_of? Numeric && !(value.kind_of? NumericWithUnits)
      NumericWithUnits.new(numeric,unit,value)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def %(value)
    if value.kind_of? NumericWithUnits
      aligned_value = align(value)
      aligned_value.numeric = aligned_value.numeric % value.numeric
      aligned_value
    elsif value.kind_of? Numeric
      NumericWithUnits.new(numeric % value,unit)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def inv_mod(value)
    if value.kind_of? NumericWithUnits
      aligned_value = align(value)
      aligned_value.numeric = value.numeric % aligned_value.numeric
      aligned_value
    elsif value.kind_of? Numeric
      NumericWithUnits.new(value % numeric,unit)
    else
      raise UnitsException.new("units mismatch")
    end
  end

end

This rearrangement preserves the correct behavior for the operations.

Promoting From Numerics

The last simple-use detail we need to deal with is making it easier to construct values with units in a generic method - basically, a way to deal with units as a variable that can be applied to numbers.

What we'll do is add one more method to Numeric, that will give us back a NumericWithUnits.

units.rb   a units pipe dream
class Numeric

  def unite(unit=nil,power=1,measure=nil)
    if !unit
      self
    else
      if (unit.kind_of? String)||(unit.kind_of? Symbol)
        units = Units.lookup(unit)
        units = units.select {|u| u.units_measure == measure} if units.size > 1
        if units.size == 0
          units_problem("usage",MissingUnitsException.new(unit),:unit=,unit)
        elsif units.size == 1
          unit = units[0]
        else
          units_problem("usage",AmbiguousUnitsException.new(unit),:unit=,unit)
        end
      elsif unit.kind_of? NumericWithUnit
        unit = unit.unit
      end
      NumericWithUnits.new(self,unit,power)
    end
  end

end

The unite method is named for unit-extend, but could also have the meaning of unite as in uniting a Numeric with a unit. It's versatile, being able to handle a string or symbol to indicate a unit name, or the unit of a given NumericWithUnit, or through the NumericWithUnits initializer, a UnitsUnit or a UnitsHash. This method tries to give you a meaningful value if you give it anything that has almost any relation to a unit. And if the unit being passed in is nil, it returns the Numeric's value.

Note that when a String or Symbol is given, the unit is looked up - which can result in a missing or ambiguous exception. If it's ambiguous, we've designed it to try to find the intended unit if a measure is provided as context.

There is one thing though - calling unite on a NumericWithUnits will fire its method_missing method and use the unite method in Numeric. Because the unite method is already sensative to units, we need to avoid this delegation and define a unite method in NumericWithUnits; otherwise given the way unite is defined in Numeric, we'll get an invalid result. The question is, what should the correct result be?

units.rb   a units pipe dream
class NumericWithUnits

  def unite(target_unit=nil,power=1,measure=nil)
    numeric.unite(target_unit,power,measure)
  end

end

Since unite in Numeric is effectively "assigning" the given unit to it's value, we'll make unite do the same thing with a NumericWithUnits instance - it'll return a NumericWithUnits with the existing numeric with the given unit substituted - even returning just the numeric part if the target unit is nil.

Our unit-uniter can be used to quickly add and adjust units as needed.

The Pipe Dream - Part 6 - Conversions

Now that we have the mechanisms in place to define and operate on unit values, it's time to figure out how to convert them. The goal is that given a NumericWithUnits instance, we need to be able to convert its units as painlessly as possible. We'll take two tacks here. In the first, we want explicit conversion we want to provide a generic method to do our work, and in the second we want a mechanism to strip down a method name that will call the generic mechanism. This way conversions can be done explictly or generically depending on need.

It's here that we'll start adding to NumericWithUnits' method_missing method in earnest.

Generic Conversion Facilities

The first thing we need to do is add a convert method. It should take units in much the same way as our unite method does and convert any existing units in a measure to the ones that are indicated. We'll build convert to return a new NumericWithUnits instance, and we'll also provide a convert! method to perform the conversion on the existing instance. While we're at it, we'll include the capability to pass in an Array of units - this makes it nice and easy to do conversions when we've built up complex unit structures.

units.rb   a units pipe dream
class NumericWithUnits

  def convert(target_units=nil)
    if target_units
      units_hash = UnitsHash.new
      if !(target_units.kind_of? Array)
        units_hash.merge!(1.unite(target_units))
      else
        target_units.each { |target_unit|
          units_hash.merge!(1.unite(target_unit)) }
      end
      align(1.unite(units_hash),false)
    else
      clone
    end
  end

  def convert!(target_units=nil)
    result = convert(target_units)
    self.numeric, self.unit = result.numeric, result.unit
    self
  end

end

Conversion is trivial since we've already written it into the align method. In order to perform many of the operations within a NumericWithUnits, we've had to align units first, which is exactly what a conversion does - just with a slightly different interface.

The only interesting thing we're doing here is building up a value to align with, since our unite method can now do the work of verifying our units for us and setting them up into a UnitsHash. Once we have this, we can just unite the the result and align the instance to it to get our converted value.

As an example, let's calculate a velocity from a distance and duration. First we'll add some more unit definitions.

definitions.rb   a units pipe dream
    Units.create :length do |m|
      m.system :english do |s|
        s.unit :name => :mile,
               :abbrev => :mi,
	       :equals => 1760.yards
      end
    end

    Units.create :time do |m|
      m.system :base do |s|
        s.unit :name => :second,
               :abbrev => :sec
        s.unit :name => :minute,
               :abbrev => :min,
	       :equals => 60.seconds
        s.unit :name => :hour,
               :abbrev => :hr,
	       :equals => 60.minutes
      end
    end

With these in place, we'll use Units to solve a simple conversion problem: if we went 140 miles in 2 hours and 35 minutes, what was our average speed in feet per second?

Nice. We didn't need to muck around with conversion factors or anything, we just expressed the quantities in Ruby and did our calculation. What's more, if we want to use some stock conversions or see other values and make other calculations, we can do all it quite easily.

Taking another look at this, the thought occurs that passing an Array into unite would be good to add to the interface - it'd just create a NumericWithUnits with its resulting units as the ones identified by calling unite recursively for each element in the Array. Though this might not be critical for external unit users, it isn't without some additional utility. And when used internally, the convert method can be simplified dramatically.

A little judicious refactoring, and

units.rb   a units pipe dream
class Numeric

  def unite(unit=nil,power=1,measure=nil)
    if !unit
      self
    else
      if (unit.kind_of? Array)
        units = UnitsHash.new
        unit.each {|u| units.merge! 1.unite(u)}
        unit = units
      elsif (unit.kind_of? String)||(unit.kind_of? Symbol)
        units = Units.lookup(unit)
        units = units.select {|u| u.units_measure == measure} if units.size > 1
        if units.size == 0
          units_problem("usage",MissingUnitsException.new(unit),:unit=,unit)
        elsif units.size == 1
          unit = units[0]
        else
          units_problem("usage",AmbiguousUnitsException.new(unit),:unit=,unit)
        end
      elsif unit.kind_of? NumericWithUnits
        unit = unit.unit
      end
      NumericWithUnits.new(self,unit,power)
    end
  end

end


class NumericWithUnits

  def convert(target_units=nil)
    target_units ? align(1.unite(target_units),false) : clone
  end

end

the convert method becomes just a simple a one-liner.

Now that we have our generic convert method out of the way, we can build an explicit handler through the method_missing method. We'll focus on a nice human way to describe conversions.

Human-Oriented Conversion Facilities

If you've used Rails, you've seen the ease with which data can be accessed through a human-oriented method naming strategy. For simple queries, methods indicate the columns that are being used to select records making code more readable and freeing the developers from the underlying details of database access. While units are typically less complex to deal with, we'd still like to give unit users with the same conveniences. We've done much of that already for unit definition, declaration and usage. We now need to go the same distance for conversion.

When dealing with conversions, there are three types of verbs we need to distinguish:

Let's look at each alternative in turn and build up code to support them.

Converting a value with units to a value with new units is analogous to what's done by our generic convert! method. What we need to do is figure out what units to convert to, tuck those into an Array, and pass it all off to convert! to do the work.

We'll make a unilateral decision here - unit names shouldn't contain underscores. While this may seem constraining, in practice it really isn't; unit names are typically short single words. That's the way they've evolved over time, and even when they start out with longer or compound names, they typically get shortened - people just don't like long names for units. What this lets us do is build method names that can be simply parsed in a method_missing method.

When we want to change the units of a value, let's say a velocity to miles and hours, the method

should do this for us. To make this happen, we need to turn this into

in method_missing.

units.rb   a units pipe dream
class NumericWithUnits

  def method_missing(method,*args)
    s = method.to_s.split '_'
    if s[0] == 'to'
      s.shift
      convert! s.select{|e| e != 'and'}
    else
      NumericWithUnits.new(numeric.send(method,*args),unit)
    end
  end

end

It's easy - we just convert the method name to a String, split it into an Array on the underscores, and if the first element is 'to', we shift it off, get rid of any split parts that were 'and', and convert the instance's units.

The order of the unit names in the method doesn't matter and we don't need to specify the 'and' - it's optional. The to_miles_and_hours, to_hours_and_miles, to_miles_hours, and to_hours_miles methods are all equivalent.

The in conversion is just like the to conversion except that the old instance is left untouched - a new NumericWithUnits is returned.

units.rb   a units pipe dream
class NumericWithUnits

  def method_missing(method,*args)
    s = method.to_s.split '_'
    if s[0] == 'to'
      s.shift
      convert! s.select{|e| e != 'and'}
    elsif s[0] == 'in'
      s.shift
      convert s.select{|e| e != 'and'}
    else
      NumericWithUnits.new(numeric.send(method,*args),unit)
    end
  end

end

This gives us

As you can see in this example, velocity1 is unaffected by the call to the conversion. And also, as before, in_miles_and_hours, in_hours_and_miles, in_miles_hours, and in_hours_miles are all equivalent. Oh, and for that matter, we can use any of a unit's identifiers in the method names to get the same result: in_mi_hr, in_mile_hour, in_mile_hours, etc. It all just works.

The per conversion is a little less trivial. This one, like in returns a new NumericWithUnits, but the conversion must be completely aligned with the given units - and also admits the possibility of inversion. Inversion? Yes.

By inversion we mean that we can use per to flip the conversion upside down. Using our example, if we have a value in length/time we'd want to convert the units using miles_per_hour or feet_per_second methods. But we also want to be able to use per to get time/length by specifying seconds_per_mile, for instance. Units should know that we've completely specified the units and inverted the relationship. For this reason, a per conversion is a bit more complicated than a simple in.

units.rb   a units pipe dream
class NumericWithUnits

  def method_missing(method,*args)
    s = method.to_s.split '_'
    if s[0] == 'to'
      s.shift
      convert! s.select{|e| e != 'and'}
    elsif s[0] == 'in'
      s.shift
      convert s.select{|e| e != 'and'}
    elsif s.select{|e| e == 'per'}.size > 0
      convert_per method
    else
      NumericWithUnits.new(numeric.send(method,*args),unit)
    end
  end

  def convert_per method
    ps = method.to_s.select{|e| e != 'and'}.join('_').split '_per_'
    raise UnitsException.new('invalid per method') if ps.size !=  2
    numerator = 1.unite(ps[0].split('_'))
    numerator_units = Set.new numerator.unit.keys
    denominator = 1.unite(ps[1].split('_'))
    denominator_units = Set.new denominator.unit.keys
    positives = unit.keys.collect{|k| unit[k] > 0 ? k : nil}.compact!
    negatives = unit.keys.collect{|k| unit[k] < 0 ? k : nil}.compact!
    numerator_positives =
      Set.new 1.unite(positives).align(numerator,false).unit.keys
    numerator_negatives =
      Set.new 1.unite(negatives).align(numerator,false).unit.keys
    denominator_positives =
      Set.new 1.unite(positives).align(denominator,false).unit.keys
    denominator_negatives =
      Set.new 1.unite(negatives).align(denominator,false).unit.keys
    if (numerator_units == numerator_positives) &&
       (denominator_units == denominator_negatives)
      convert((ps[0]+'_'+ps[1]).split('_'))
    elsif (numerator_units == numerator_negatives) &&
       (denominator_units == denominator_positives)
      convert((ps[0]+'_'+ps[1]).split('_'))**-1
    else
      raise UnitsException.new('invalid per units')
    end
  end

end

This may seem a little daunting, but it's really not so bad. First we make method_missing call the convert_per method if 'per' is found in the method name. When we enter compare_per, we make sure 'per' is between two sets of units. We pull out the first set and make the numerator, and the second we make the denominator. Next we separate out the sets of units in this instance that have positive or negative exponents. We then align our positives and negatives to the numerator and denominator - and then we compare. If the numerator and denominator are aligned to the positives and negatives respectively, we're a normal conversion. If the numerator and denominator are aligned to the negatives and positives respectively, we're an inverted conversion. Otherwise, we've got units measures in the instance that don't match the ones implied by the method, so we raise a UnitsException.

It's not trivial only because we have a couple layers of matching going on. But it does give us what we want.

We have to. We have in. We have per. But it's still got one small bit of clunckiness. If we were to say something like

it's pretty clear that what we want to do is to get back a new NumericWithUnits with a value of 8.5 feet - exactly what in would give us. Putting the in_ at the beginning of the method name shouldn't be strictly necessary. But how should we do this? We should create a new convert method that decodes the method name, matches it up with the units we have to make sure everything's valid and does the conversion. And then how do we funnel methods that aren't meant to be conversions to the numeric part? Easy. We just reorganize using a begin-rescue to encapsulate our conversions.

units.rb   a units pipe dream
class NumericWithUnits

  def method_missing(method,*args)
    begin
      s = method.to_s.split '_'
      if s[0] == 'to'
        s.shift
        convert! s.select{|e| e != 'and'}
      elsif s[0] == 'in'
        s.shift
        convert s.select{|e| e != 'and'}
      elsif s.select{|e| e == 'per'}.size > 0
        convert_per method
      else
        convert s.select{|e| e != 'and'}
      end
    rescue
      NumericWithUnits.new(numeric.send(method,*args),unit)
    end
  end

end

Sneaky, huh? We just assume it's a conversion and we try it! If it's not, that's okay because we'll raise an exception, be rescued and give the instance's numeric a try at the end.

You know though, we did uncover a small bit of ugliness when we were trying thing out here. If you look back you'll see that to get 55 feet per second, we had to express it as

It'd be much more natural to be able to express this as

Well, this is really quite simple now that we're so familiar with method_missing. We just have to push it onto Object's method_missing method.

units.rb   a units pipe dream
class Object

  alias :method_missing_before_units :method_missing

  def method_missing(method,*args)
    begin
      1.unite(method.to_s.split('_').select{|e| e!= 'and'})
    rescue Exception
      method_missing_before_units(method,*args)
    end
  end

end

And if we slip in another flourish, we can use the per form here quite naturally.

We get this by pushing on Numeric's method_missing code.

units.rb   a units pipe dream
class Numeric

  def method_missing(method,*args)
    if Units.defining?
      reference = Units.create_forward_reference(method,Units.defining?)
      begin
        value = Units.convert self, method
        Units.remove_forward_reference reference
        value
      rescue MissingUnitsException
        Units.hold_forward_reference
      rescue UnitsException => exception
        units_problem("definition",exception,method,args)
      rescue Exception
        method_missing_before_units(method,args)
      end
    else
      begin
        s = method.to_s.split('_').select{|e| e != 'and'}
        if s.select{|e| e == 'per'}.size > 0
          ps = s.join('_').split '_per_'
          raise UnitsException('invalid per method') if ps.size !=  2
          unite(ps[0].split('_'))/(1.unite(ps[1].split('_')))
        else
          unite s
        end
      rescue UnitsException => exception
        units_problem("usage",exception,method,args)
      rescue Exception
        method_missing_before_units(method,*args)
      end
    end
  end

end

We're dividing the first part of the per expression by the second part if it's well formed, but notice that we took the liberty of simplifying the non-defining case considerably, just calling the unite method rather than going through Units' convert method as we did previously.

There's one more place where a per conversion is useful - when we want to obtain a pure conversion factor between two units in the same Measure. For instance, if we say

we want to get back a value of 5280. For this, we slip a little more code into Object's method_missing method.

units.rb   a units pipe dream
class Object

  def method_missing(method,*args)
    begin
      s = method.to_s.split('_')
      if s.select{|e| e == 'per'}.size > 0
        per_ratio method
      else
        1.unite(s.select{|e| e!= 'and'})
      end
    rescue Exception => exception
      method_missing_before_units(method,*args)
    end
  end

  def per_ratio method
    ps = method.to_s.select{|e| e != 'and'}.join('_').split '_per_'
    raise UnitsException('invalid per method') if ps.size !=  2
    value = (1.unite ps[1].split('_')) / (1.unite(ps[0].split('_')))
    raise UnitsException.new("per ratio has units") if
      value.kind_of? NumericWithUnits
    value
  end

end

If the method has a per in it, we split it and do a division and return the division. Two things to notice though. First, we raise an UnitsException if the result of the division has units - that is, if it's a type of NumericWithUnits. Remember, that when the result of the division of two values with units is unitless (as in this case when the Measures cancel out) that the result is demoted to a pure Numeric. And second, look closely at the division. Why are we dividing the second value by the first? Well, because when we say something like "feet per mile", what we really mean is mile.in_feet/feet. It doesn't make pure mathematical sense - it's just the way people say it! Remember that for people, it's the context that matters: when the measures are the same on both sides of the per, the result of the division is inverted. Per changes its meaning in different contexts.

The Pipe Dream - Part 7 - Improved Rendering

Alas, our to_s method in NumericWithUnits is a bit lacking. Often, values in a measure are expressed not just in a single unit, but broken down into related pieces. For instance, while

is perfectly reasonable, we'd also like the ability to have it display as

or

This really isn't so hard to do - what's hard is figuring out a good way to express it. What we'll do is add the definition of formats to our infrastructure, and allow to_s to take a format as an argument. This strategy will let us define as many formats as we want, and they'll be carried right along with the unit definitions.

Since we only do this sort of formating within a given system of units within a measure, it make sense to define the functionality there. Let's consider the length measure and a way to get feet, inches, and thirty-seconds of an inch. What would be nice is something like

units.rb   a units pipe dream
    Units.create :length do |m|
      m.system :english do |s|
        s.format :name => :feet_inches_and_32s,
                 :format => "#{whole_feet} #{remaining_inches_with_fraction 32}"
      end
    end

and then do

Yeah, that would be really nice. Unfortunately, it does have a few problems, not the least of which the String itself would be evaluated during definition - even if we could come up with good definitions for getting whole and remaining parts (and whatever other interesting types of slicing and dicing we might need) of a NumericWithUnits in the right order. No, unfortunately an abstraction like this is just a little too abstract. What should we do?

Well, since we're in Ruby, why not use Ruby? Let's see. Instead of defining a String that gets evaluated when the system is being defined, let's use a closure - basically a nice Ruby way to define a function - and see where that gets us. The lambda method takes a block and converts it to a callable proc; the proc is Ruby's mechanism for closures.

units.rb   a units pipe dream
    Units.create :length do |m|
      m.system :english do |s|
        s.format :name => :feet_inches_and_32s,
                 :format => lambda { |u|
                        "#{u.whole_feet} #{u.remaining_inches_with_fraction 32}" }
      end
    end

Ok, so far so good. But how about those other pesky functions in there? There could be lots of them! How many of those are we going to have to write?

Well, the short answer is none, unless there's a good reason. What we'll do is just add processing to the block.

units.rb   a units pipe dream
    Units.create :length do |m|
      m.system :english do |s|
        s.format :name => :feet_inches_and_32s,
                 :format => lambda { |u|
                                     ru = u.round_to_nearest(1.inch,32)
                                     feet = ru.feet.floor
                                     inches = (ru-feet).inches
                                     "#{feet} #{inches.with_fraction 32}" }
      end
    end

Hey - that's pretty close! We get to use most of what we already had. It looks like we need to just add a specialy method that will render the numeric part of the unit a little differently. The code will be cake!

Let's start writing...

units.rb   a units pipe dream
class UnitsSystem

  attr_reader :formats

  def initialize(units_measure,name)
    @units_measure = units_measure
    @name = name
    @formats = MethodicHash.new
  end

  def format(options)
    @formats[options[:name]] = options[:proc]
  end

end


class NumericWithUnits

  def format(name=nil)
    if name == nil
      to_s
    else
      raise UnitsException.new("system not explicit") if (system == nil)
      format = system.formats[name]
      raise UnitsException.new("missing format") if format == nil
      format.call(self)
    end

end

Oops. Our definition of the format in UnitsSystem looks fine, but our call to system qualifying the first raise is a big problem. Consider - a measure is independent of systems! For example, the same length measured in the English system is equivalent to that length measured in the Metric system - this is just a way of rendering! While it's fine to define formats at the system level, they should be accessed through the measure level. Let's do a little quick change.

units.rb   a units pipe dream
class UnitsMeasure

  attr_reader :formats

  def initialize
    @formats = MethodicHash.new
  end

  def format(options)
    @formats[options[:name]] = options[:format]
  end

end


class UnitsSystem

  def format(options)
    @units_measure.format(options)
  end

end


class NumericWithUnits

  def format(name=nil)
    if name == nil
      to_s
    else
      raise UnitsException.new("measure not explicit") if (measure == nil)
      format = measure.formats[name]
      raise UnitsException.new("missing format") if format == nil
      format.call(self)
    end

end

This is interesting. By coding it this way, storing the formats in the UnitsMeasure and defining a format method there, and calling that method from a UnitsSystem using a format method that takes the same arguments, we can potentially define formats at the UnitsMeasure level as well as within a UnitsSystem. Of course, we need to define the measure method in NumericWithUnits, but this is as easy as combining the UnitMeasures of the Units in the UnitsHash. We'll build up a measure derivation and, since we need to make sure the "artificial" derivation matches a measure, we must enhance the find_by_derivation method in Units.

units.rb   a units pipe dream
class UnitsHash

  def measure
    measure = UnitsMeasure.new
    each { |unit,power|
      measure.merge_derivation unit.units_system.units_measure => power }
    Units.find_by_derivation(measure.derived)
  end

end


class Units

  def Units.find_by_derivation(derivation)
    matches = @@measures.values.uniq.select { |measure|
      if measure.derived
        measure == derivation
      elsif derivation.size == 1
        a = derivation.to_a[0]
        a[0] == measure && a[1] == 1
      else
        false
      end
    }
    case matches.size
      when 0 then nil
      when 1 then matches[0]
      else raise UnitsException.new(
          "Multiple UnitsMeasures with same derivation found")
    end
  end

end

The remainder of work is specifying any additional renderers for the numeric part of a NumericWithUnits. But there is some great fallout from this. When we think about it, whatever we'd like to do to the numeric part, we'd like to be able to do to a Numeric. This part of the work on Numeric actually transcends units and we'll take a general approach to the solution. Lets code our round_to_nearest and with_fraction methods.

We'll first throw together our fraction printer and specialized rounder.

units.rb   a units pipe dream
class Numeric

  def with_fraction(denominator=1,separator='-',show_zero=false)
    sign = self <=> 0
    unsigned = self*sign
    whole = (((unsigned*denominator).round)/denominator).floor
    numerator = ((unsigned-whole)*denominator).round
    "#{sign*whole}#{(numerator != 0)||show_zero ?
        "#{separator}#{numerator}/#{denominator}" : ""}"
  end

  def round_to_nearest(denominator=1)
    ((self*denominator).round)/(1.0*denominator)
  end

end

Aside from some generality and the fact that because we're using the floor function we have to pay attention to the sign of the numeric, our with_fraction prints whole and fractional parts of numbers pretty nicely.

The whole reason we need a round_to_nearest method is to ensure we get the right values all the way up to the highest unit. Hierarchical units are like the odometer on your car: rounding may cascade up to push you up to the next unit. If you don't take care, and just render the pieces separately before you rationalize the whole numeric, you may not do rounding correctly.

See? If we just did a straight rounding on the feet or the inches, we'd lose the 32nds. It's just these sort of methods that, while we want to use them for formatting NumericWithUnit values, they make good sense to just add to the Numeric

From here it's just a short hop to get what we need - but like any other method in Numeric we want to use from a NumericWithUnits, we'll call the method on the numeric part from the method_missing. The difference is, we'll take a look at what the call returns: if it's a kind of String value, we'll tack on the units.

units.rb   a units pipe dream
class NumericWithUnits

  def method_missing(method,*args)
    begin
      s = method.to_s.split '_'
      if s[0] == 'to'
        s.shift
        convert! s.select{|e| e != 'and'}
      elsif s[0] == 'in'
        s.shift
        convert s.select{|e| e != 'and'}
      elsif s.select{|e| e == 'per'}.size > 0
        convert_per method
      else
        convert s.select{|e| e!= 'and'}
      end
    rescue Exception
      value = numeric.send(method,*args)
      if (value.kind_of? String)
        "#{value} #{unit.to_s(numeric)}"
      else
        NumericWithUnits.new(value,unit)
      end
    end
  end

end

So, let's give it a shot.

Now before you start casting aspersions, I already know there are easy holes to poke into this. But look, with the power of Ruby behind you, you can write your own complex-but-generalized rendering methods and keep them associated with the unit definitions - not buried in custom code. Rendering is just as much a part of a Units package as all of the arithemtic and conversions, and now everything is brought together in such a way that we don't have to sweat it as much.

Rendering Derived Units

One of the things that has been overlooked until now is that we don't handle derived units correctly. In our headlong rush to add functionality, this got overlooked. While we can do:

we don't get the reduction from ml/cm to cm^2 that we should have expected. What did we miss? Where did we go wrong?

Well, in truth, we didn't go wrong anywhere and we didn't miss anything - we just didn't write tests for derived unit capabilities. If we were starting from behavioral specifications, we probably would have captured this. But we weren't starting from specs. Remember, this is a pipe dream. Should we have written specs first? Perhaps. But this ignores one of the fundamental truisms of human endeavor: You almost never get it right. Translated to the problem at hand: if we had spec'ed it first, we would have missed something. If not this behavior, we would have missed something else. Why do anything in the first place then, you may ask, if we're never going to get anything right? Because two other fundamental truisms come into play, namely: Nothing gets done if you give up and Perfectionism is too expensive

If we look closely at the problem, the answer is fine - it's just that we really want an alternative that is reduced for calculating and the option to create an unreduced version for output. So, what do we do to fix this? We'll dive in and approach the issues we find one at a time. First, we'll look at the conversion problem above.

We like the declarative statement just fine. When we set the unit to be milliliters, we want to get milliliters back out. It's the conversion that's troublesome. When we divided by centimeters, we wanted the milliliters to be treated as a volume from which we could factor out a length. The problem seems to be that even though we're dealing with a unit derived from length, we don't take that into account during calculation. What we need to do is expand the units we're calculating with whenever we've got a derived unit.

units.rb   a units pipe dream
class UnitsHash

  def derived?
    keys.select{|unit| unit.units_system.units_measure.derived != nil}.size > 0
  end

  def reduce
    factor = 1.0
    new = UnitsHash.new
    each do |unit,power|
      if unit.units_system.units_measure.derived != nil
        factor *= unit.equals.numeric
        new.merge!(unit.equals,power)
      else
        new.merge! unit
      end
    end
    factor.unite new
  end

end


class NumericWithUnits

  def derived?
    unit.derived?
  end

  def reduce
    numeric*unit.reduce
  end

  def <=>(value)
    if derived?
      reduce <=> value
    elsif value.kind_of? NumericWithUnits
      if value.derived?
        self <=> value.reduce
      else
        align(value).numeric <=> value.numeric
      end
    elsif value.kind_of? Numeric
      numeric <=> value
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def approximately_equals?(value,epsilon=Numeric.epsilon)
    if derived?
      reduce.approximately_equals?(value,epsilon)
    elsif value.kind_of? NumericWithUnits
      if value.derived?
        self.approximately_equals?(value.reduce,epsilon)
      else
        align(value).numeric.approximately_equals?(value.numeric,epsilon)
      end
    elsif value.kind_of? Numeric
      numeric.approximately_equals?(value,epsilon)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def +(value)
    if derived?
      reduce+value
    elsif value.kind_of? NumericWithUnits
      if value.derived?
        self+value.reduce
      else
        aligned_value = align(value)
        aligned_value.numeric += value.numeric
        aligned_value
      end
    elsif value.kind_of? Numeric
      NumericWithUnits.new(numeric+value,unit)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def *(value)
    if derived?
      reduce*value
    elsif value.kind_of? NumericWithUnits
      if value.derived?
        self*value.reduce
      else
        extend(value,1)
      end
    elsif value.kind_of? Numeric
      NumericWithUnits.new(numeric*value,unit)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def /(value)
    if derived?
      reduce/value
    elsif value.kind_of? NumericWithUnits
      if value.derived?
        self/value.reduce
      else
        extend(value,-1)
      end
    elsif value.kind_of? Numeric
      NumericWithUnits.new(numeric/value,unit)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def %(value)
    if derived?
      reduce%value
    elsif value.kind_of? NumericWithUnits
      if value.derived?
        self%value.reduce
      else
        aligned_value = align(value)
        aligned_value.numeric = aligned_value.numeric % value.numeric
        aligned_value
      end
    elsif value.kind_of? Numeric
      NumericWithUnits.new(numeric % value,unit)
    else
      raise UnitsException.new("units mismatch")
    end
  end

  def inv_mod(value)
    if derived?
      reduce.inv_mod value
    elsif value.kind_of? NumericWithUnits
      if value.derived?
        self.inv_mod value.reduce
      else
        aligned_value = align(value)
        aligned_value.numeric = value.numeric % aligned_value.numeric
        aligned_value
      end
    elsif value.kind_of? Numeric
      NumericWithUnits.new(value % numeric,unit)
    else
      raise UnitsException.new("units mismatch")
    end
  end

end

We need to check the operands of any numeric operation where another NumericWithUnits is involved, reducing each prior to applying the operation if they are derived. For other operations, like exponentiation or ones that just operate on a Numeric, we can leave things alone since they don't require a reduction. Doing reduction this way means we'll adjust our values to underived units whenever we do calculations.

Let's see how we're doing so far:

The numbers are right and the units match the rules we laid down about what unit takes precedence in an operation - everything looks good. So, now we just need to switch to the intended units during conversion. Let's work on a value in cubic centimeters and try to convert it milliliters.

No luck, but we expected that. Our problem is that the code doesn't look at the units we're derived from - we need to use what they're equal to and "back-convert". To do this we must create a more sophisticated alignment when we're working with derived units, keeping our non-derived-unit version around for fast performance.

units.rb   a units pipe dream
class NumericWithUnits

  def self.derived_align_type=(type)
    @@derived_align_type = type
  end

  def self.derived_align_type
    @@derived_align_type
  end

  @@derived_align_type = :whole_powers

  def align(target,all=true,type=@@derived_align_type)
    if !derived? && !target.derived?
      simple_align(target,all)
    else
      power_align(target,all,type)
    end
  end

  def simple_align(target,all=true)
    factor = 1
    target_unit = UnitsHash.new
    unit.each do |tu,tv|
      su = target.unit.keys.select {|u|
          u.units_measure.equal? tu.units_measure }
      if su.size == 1
        factor *= ((1.0*tu.equals.numeric)/su[0].equals.numeric)**tv
        target_unit[su[0]] = tv
      else
        target_unit[tu] = tv
      end
    end
    raise UnitsException.new("units mismatch") if
       all && (target.unit != target_unit)
    NumericWithUnits.new(numeric*factor,target_unit)
  end

  def can_align(target,exact=true)
    ru, tru = reduce.unit, target.reduce.unit
    !exact || (ru == tru)
  end

  def reduce_power(p,f,type)
    if type == :whole_powers
      pa = p.abs
      (p == 0) ? [p,f,0] : (pa < f) ? [p, f, pa/p] : [p, f, p/f]
    else
      (p == 0) ? [p,f,0] : [p, f, (1.0*p)/f]
    end
  end

  def common_power(ps)
    ps.values.collect {|a| a[2]}.inject {|p,e| p.abs < e.abs ? p : e}
  end

  def revise_power(rp,p)
    [rp[0], rp[1], p, rp[0]-p*rp[1]]
  end

  def power_align(target,all=true,type=@@derived_align_type)
    raise UnitsException.new("units mismatch") unless can_align(target,all)
    factor = 1
    target_unit = UnitsHash.new
    unit_reduce = reduce
    target.unit.each do |tu,tv|
      t_u = {}
      tt_u = {}
      tu_reduce = tu.equals.reduce
      found = tu_reduce.unit.each do |ttu,ttv|
	su = unit_reduce.unit.keys.select {|u|
          u.units_measure.equal? ttu.units_measure }
        break false if su.size == 0
	tt_u[ttu] = reduce_power(unit_reduce.unit[ttu],ttv,type)
      end
      if found
        cp = common_power(tt_u)
        tt_u.each {|k,v| tt_u[k] = revise_power(v,cp)}
      	t_u[tu] = tv**cp
	t_factor = tu_reduce.numeric**cp
	target_unit[tu] = cp
	factor *= t_factor/(tu.equals.numeric**cp)
	tt_u.each {|k,v|
	  unit_reduce.numeric /= t_factor
	  unit_reduce.unit[k] = v[3]}
      end
    end
    unit_redux = unit_reduce.simple_align(self,false)
    result_unit = target_unit.merge(unit_redux)
    raise UnitsException.new("units mismatch") if
       all && (target.unit != result_unit)
    NumericWithUnits.new(factor*unit_redux.numeric,result_unit)
  end

end

Now when we align, we check to see if either the aligner or alignee is derived. If not, we just do a simple_align; otherwise, we do a power_align.

The power_align method uses reduced units to perform an alignment. First we check to make sure that we can do an alignment - if not, we fail abruptly. Then we walk through the set of target units; we reduce each in turn and look to if there are sufficient reduced units left to convert in the instance we're aligning to do a conversion of that unit. If so, we add the target, and reduce the instance's reduced units appropriately. At the end, we merge in the remaining reduced units after they've been re-aligned with the instance's original units. Of course, if we had to align all the units - because we're adding things together, for instance - we'll fail if things didn't come out right.

Other than this, there is one variation we wish to maintain when we power align - to use whole or fractional units. The choice really is arbitrary for calculation, but when rendering some would consider whole-number-dimensioned units to be prettier than fractionally-dimensioned units. We allow the method that will be used to be set at the class level for convenience.

And what's more,

Triumph! But there is a small consideration that we still need to take care of. Consider doing a conversion where you don't want to exhaust a measure too early - if you did, you'd have nothing left to draw from when aligning to subsequent targets. This may not happen very often, but it still could nonetheless. The trick is that we need to take the power of the resultant units into consideration during the alignment.

What we need is a way to specify the precise units and powers we'd like the resulting NumericWithUnits to have. For this, we'll call up the align method directly and give it an Array of the pieces to which we want to be aligned. For example, let's say we want milliliters over meter. We would want to do

We just need to take an Array of NumericWithUnits as a target and use each one (and it's power) to form the result.

units.rb   a units pipe dream
class NumericWithUnits

  def align(target,all=true,type=@@derived_align_type)
    if target.kind_of? Array
      piece_align(target)
    elsif !derived? && !target.derived?
      simple_align(target,all)
    else
      power_align(target,all,type)
    end
  end

  def piece_align(pieces,type=@@derived_align_type)
    factor = 1
    target_unit = UnitsHash.new
    unit_reduce = reduce
    pieces.each do |p|
      p.unit.each do |tu,tv|
        t_u = {}
        tt_u = {}
        tu_reduce = tu.equals.reduce
        found = tu_reduce.unit.each do |ttu,ttv|
	  su = unit_reduce.unit.keys.select {|u|
            u.units_measure.equal? ttu.units_measure }
          break false if su.size == 0
	  tt_u[ttu] = reduce_power(unit_reduce.unit[ttu],ttv,type)
        end
        if found
          tt_u.each {|k,v| tt_u[k] = revise_power(v,tv)}
      	  t_u[tu] = tv
	  t_factor = tu_reduce.numeric**tv
	  target_unit[tu] = tv
	  factor *= t_factor/(tu.equals.numeric**tv)
	  tt_u.each {|k,v|
	    unit_reduce.numeric /= t_factor
	    unit_reduce.unit[k] = v[3]}
        end
      end
    end
    raise UnitsException.new("units mismatch") if unit_reduce.unit.has_units?
    NumericWithUnits.new(factor*unit_reduce.numeric,target_unit)
  end

end


class UnitsHash

  def has_units?
    (self.select {|k,v| v != 0.0}).size > 0
  end

end

Now we run the power reduction for each piece, and instead of asking for the common power, we just use the power that was passed in. At the end, if there are any units left over, we throw an exception. For this, we want an exact match.

It's arguable that we should try to use the convert method instead of exposing the align method directly. However, there's something more subtle going on here - specifically, we don't want the elements of the array combined before we align, which is what convert does. We want each applied in turn, incrementally aligning the object to each element of the Array. We may not do it often, but when we want to output in particular units that don't come out of the calculations naturally, this will do the job.

Now we have all we need to render derived units correctly.

Just the Right Unit

When I've used units in a program's output up to now, most of the time they've been specified to me by someone else, or in a particular problem. "Answer the problem in meters per second," or "express the weight in pounds", or "how many tablespoons in a cup?" Sometimes, the choice is based on convention, sometimes on preference, and sometimes it's ad hoc. When we're given the choice, which units do we choose?

This is a good question - but because choice is involved, getting a answer can be tricky. The best we can really do is allow the user to give us some options and constraints and try to make a reasonable determination.

Since all the alternatives to choose from are effectively equivalent - all equal to each other as far as measurement is concerned - the constraints are based on weighting the units and the value of the numerics. For instance, if a units user were to present us with four unit choices each weighted by preference, and a preferred range for the numeric part of our NumericWithUnits, we should be able to return the units that provide the best fit.

What we'll do is set up a mechanism that takes constraints, weights and an Array of sample values that will return the ranked alternatives.

units.rb   a units pipe dream
class Units

  def Units.rank(unit_choices = {}, samples = [], &numeric_ranker)
    if block_given?
      scores = {}
      samples.each {|s|
        unit_choices.each {|uc,w|
          puts s.convert(uc)
          scores[uc] = (scores[uc] || 0) + w*(yield s.convert(uc).numeric) } }
      scores.sort {|e1,e2| -(e1[1] <=> e2[1])}
      scores.collect {|s| s[0]}
    else
      rank(unit_choices,samples) { |n|
        e = ((((n.abs)-5.5).abs-4.5).at_most(0))
        (e == 0) ? 0 : (e == 1) ? 0 : -(e + 1/(1-e)) }
    end
  end

end

Alright. A little explanation is in order. We pass in a Hash of unit choices and a weight multiplier for each choice. For the sake of illustration, we'll pick

What you see here is that scores for meter/minute are weighted twice as heavily as scores for miles/hour or kilometers/hour. The position we've taken is that the highest score wins, so scores should be more negative the further their values are from the ideal scheme - I've set it up so everything non-zero will be a negative, errors are the only values that will have weight. However, the algorithm is completely general - you can make it do whatever you'd like it to do.

Next we throw together a few samples:

The idea is that these are values are representative for what we expect to get when we want "just the right units". We may have a sample set containing one element or a thousand - it doesn't matter. The unit that scores highest overall will be ranked first.

We've set up the algorithm to take the choices and samples, and a block that will give us a score based on the numeric part of a sample value when it's units are converted to one of the choices. As you can see, if we don't provide a block, the method will be re-sent with the curious-looking block provided. What this block does is scores zero if a numeric is between one and ten, and a value of -(e + 1/(1-e)) when it's outside that range. The idea is that values with a single leading digit are preferred - what this does is makes the error value from one down to zero look like the range from 10 up to infinity - the farther you are from the range logarithmically speaking, the greater the error, and the weight magnifies the resulting error. We also exclude a plain zero (when e == 1) since it's a single digit.

So, we give it a try and

So meters/minute is the best choice for this particular set of samples - despite the fact that we weighted the error twice as high! Given our samples, meters/minute is just the right unit to use.

Oh, by the way, you may not be familiar with at_most (or at_least.) No problem. They were schemed up to provide the max and min functionality available in most numerical libraries - at_most limits the a value on the upside, at_least limits it on the downside.

units.rb   a units pipe dream
class Numeric

  def at_most(m=0)
    m < self ? m : self
  end

  def at_least(m=0)
    m > self ? m : self
  end

end

Kudos to Ruby for at least being extensible enough to add functionality that's missing. It'd be nice to have everything you could ever want already baked into the language, but at least you aren't locked out from adding the parts you need.

The Pipe Dream - Part 8 - The Need for Speed

I'll be the first to admit we've added a lot of goo to numbers in Ruby. While the goo is good for making values with units actually work, it's going to slow computations down at least a little. I think we all need the unit-awareness, and it's incredible useful to have it at our fingertips, but how gooey is Ruby now? There's all the goo for units themselves, and method_missing, and... hmmm... let's take a look at how big the hit the units goo really is.

Working with Unitless Values

The best way to look at this is, I suppose, is to consider what happens when we're working with unitless values. We should be satisfied with the system when the values we're working with do have units - but we need to make sure that when they don't that we didn't handcuff ourselves.

So lets take stock here. We'll look at the methods we've added or changed that would get called if we're operating on unitless values in unitless contexts.

Wait just a second. That's it?

If we're not using units we just take a hit when the methods we're using aren't defined and we have to drop into method_missing - and these sections fail quickly because we've assumed we're not working with units so the missing methods will have non-units sorts of names, right? That's not so bad. After all, if the control flow of the program goes through method_missing, then we should expect it may take some extra time to get to the real guts of the computations. What we're seeing is that units put a nearly insignificant load on the related objects when we're not using them. That's very good news.

Well, there is a little more, but it's a startup issue. We still may be feeding a big set of units to the system to "prime the units pump" so to speak, establishing the baseline units into our Ruby program. But of course, we haven't defined this yet, and we can even make the baseline unit loading be optional.

No, it really looks like the hit is small and that most of the time, working with unitless values will happily bypass our units system.

Defering Unit Application

As I alluded to in the beginning of this excursion into the creation of a units infrastructure, down through the years programmers have avoided explicitly using units in their code. Why? Well, besides the fact that they've perhaps only been explicitly available in a handful of languages (and after a quick survey, I still can't find anything like this in the mainstream) the explicit use of units is slower than just using numbers directly. To this I concede, absolutely! Look at all the additional definitions and lookups and alignments and extensions we've had to work our way through! Making units relatively transparent has not been easy, and there certainly is a performance penalty when using them.

But software developers are sharp thinkers. Disregarding units until values are ready to be output is still a good idea in many situations. By doing this you keep the numeric calculations running fast. Sure, errors can creep in - insidious ones that are hard to track down - but once everything's tested and certified, writing code this way should work fine.

The units package we've developed doesn't keep us from doing this, of course. We can unite numerics and units at any time during the process - and at any time we can pull out the numeric part of a NumericWithUnits and use it in any way we'd like. Given our ability to directly use scaling conversion factors like

or apply transforming ones like

we can apply units late in the process and keep our numeric processing clean and fast. Just keep in mind that the debugging of complex unitless code isn't necessarily easy. I've been there.

I've transposed digits or slipped powers-of-ten and been off on a conversion factor more times than I can count. While this has (thankfully) always been caught during testing, it still has slowed down project deliveries. If you've ever been up late wondering why the heck the values aren't coming out right only to find you slipped up doing a conversion and it's been carried through a few dozen different transformations that have magnified the error to the point where the result bears no resemblance to what the expected, you know exactly what I'm talking about. And if you haven't, consider yourself lucky. Your only recourse is to become very good at copying 9-digit numbers from the back pages of reference books.

The bottom line is that if you're going to be doing a lot of mathematical calculations, sometimes it makes sense to strip the units off your values before and re-unite them afterwards. For instance, if you're going to multiply a bunch of big matrices together, you know the units of the values you're putting in and you know what the resulting units will be, strip the units first and reapply them when you finish - it'll run faster. But just be careful - one unitless number looks like any other. If you're sloppy, you'll be in trouble.

The Pipe Dream - Interlude - Defining Units and Taking Stock

Okay. Before we go further, I need to come up for air to try a few things and see where we are so far.

We defined a lot of framework and wrote a lot of unit tests along the way. Everything we've built passes muster, but is everything we need there? Do we need a course correction? One way to find out... let's try to define some units for keeps, the kind of thing we'll want to come along for free when we write unit-sensitive Ruby code.

We'll start with length:

definitions.rb   a units pipe dream
Units.create :length do |m|
  m.system :english do |s|
    s.unit :name => :inch, :plural => :inches, :abbrev => :in
    s.unit :name => :foot, :plural => :feet, :equals => 12.inches, :abbrev => :ft
    s.unit :name => :yard, :equals => 3.feet, :abbrev => :yd
    s.unit :name => :mile, :equals => 5280.feet, :abbrev => :mi
    s.unit :name => :nautical_mile, :equals => 1852.meters, :abbrev => :nmi
    s.format :name => :feet_inches_and_32s,
      :format => lambda { |u|
                          ru = u.round_to_nearest(1.inch,32)
                          feet = ru.feet.floor
                          inches = (ru-feet).inches
                          "#{feet} #{inches.with_fraction 32}" }
  end
  m.system :metric do |s|
    s.unit :name => :meter, :equals =>39.37.inches, :abbrev => :m, :greek => :ten
    s.unit :name => :angstrom, :equals => 0.1.nanometers, :abbrev => :A
  end
  m.system :old_english do |s|
    s.unit :name => :fathom, :equals => 2.yards
    s.unit :name => :chain, :equals => 22.yards
    s.unit :name => :furlong, :equals => 660.feet
    s.unit :name => :league,  :equals => 3.nmi
  end
  m.system :astronomical do |s|
    s.unit :name => :astronomical_unit, :equals => 149598000.kilometers, :abbrev => :AU
    s.unit :name => :light_year, :equals => speed_of_light*seconds_per_year, :abbrev => :ly
    s.unit :name => :parsec, :equals => 3.262.light_years, :abbrev => :pc
  end
end

This is a decent set to go with, english system units, metric units with the greek prefixes and angstroms, some old archaic english units for fun, and some huge-distance astronomical units. Let's wire it in, run a unit test and... damn.

We blew up because we referenced the speed of light and subsquently light year before they were defined. When we added our forward reference mechanism to the defining state, we did Numeric - not an arbitrary Object. Apparently we need to discriminate between Numerics and other Objects, since we may have objects coming down the pipe. We need to expand on how Object handles missing units.

units.rb   a units pipe dream
class Object

  def method_missing(method,*args)
    begin
      s = method.to_s.split('_')
      if s.select{|e| e == 'per'}.size > 0
        per_ratio method, args
      else
        convert_unit_value method, args
      end
    rescue Exception => exception
      method_missing_before_units method, args
    end
  end

  def convert_unit_value(method,*args)
    if Units.defining?
      reference = Units.make_forward_reference(method,Units.defining?)
      begin
        value = Units.convert 1, method
        Units.release_forward_reference reference
        value
      rescue MissingUnitsException
        Units.hold_forward_reference
      rescue UnitsException => exception
        units_problem("definition",exception,method,args)
      rescue Exception
        method_missing_before_units(method,args)
      end
    else
      begin
        Units.convert 1, method
      rescue UnitsException => exception
        units_problem("use",exception,method,args)
      rescue Exception
        method_missing_before_units(method,args)
      end
    end
  end

  def per_ratio(method,*args)
    if Units.defining?
      reference = Units.make_forward_reference(method,Units.defining?)
      puts "trying #{method} in #{Units.defining?}"
      begin
        value = convert_per_ratio method
	Units.release_forward_reference reference
	value
      rescue MissingUnitsException
        Units.hold_forward_reference
      rescue UnitsException => exception
        units_problem("definition",exception,method,args)
      rescue Exception
        method_missing_before_units(method,args)
      end
    else
      convert_per_ratio method
    end
  end

  def convert_per_ratio(method)
    ps = method.to_s.select{|e| e != 'and'}.join('_').split '_per_'
    raise UnitsException('invalid per method') if ps.size !=  2
    value = (1.unite ps[1].split('_')) / (1.unite(ps[0].split('_')))
    raise UnitsException.new("per ratio has units") if
      value.kind_of? NumericWithUnits
    value
  end

  def units_problem(state,exception,method,args)
    raise exception
  end

end

That takes care of that. We'll save forward references when we're defining units that resolve to Object's method_missing. So now, let's keep going. Let's take care of the time-based entries.

definitions.rb   a units pipe dream
Units.create :time do |m|
  m.system :base do |s|
    s.unit :name => :nanosecond, :equals => 0.000000001.seconds, :abbrev => [:ns, :nsec]
    s.unit :name => :microsecond, :equals => 0.000001.seconds, :abbrev => [:us, :usec]
    s.unit :name => :millisecond, :equals => 0.001.seconds, :abbrev => [:ms, :msec]
    s.unit :name => :second, :abbrev => [:s, :sec]
    s.unit :name => :minute, :equals => 60.seconds, :abbrev => [:m, :min]
    s.unit :name => :hour, :equals => 60.minutes, :abbrev => [:h, :hr]
    s.unit :name => :day, :equals => 24.hours, :abbrev => [:d, :dy]
    s.unit :name => :week, :equals => 7.days, :abbrev => [:w, :wk]
  end
  m.system :common do |s|
    s.unit :name => :month, :equals => [30.days, 4.weeks],
           :abbrev => [:m, :mo]
    s.unit :name => :year, :equals => [365.days, 12.months, 52.weeks],
           :abbrev => [:y, :yr]
  end
  m.system :long do |s|
    s.unit :name => :decade, :equals => 10.years
    s.unit :name => :century, :plural => :centuries, :equals => 100.years
    s.unit :name => :millenium, :plural => :millenia, :equals => 1000.years
  end
  m.system :old_english do |s|
    s.unit :name => :fortnight, :equals => 2.weeks
  end
end

We once again wire, run, and... damn. Why did we blow up this time?

It's because of the way we defined the month and year. Look at how funky the equals element are - they're Arrays, which are definitely not handled correctly anywhere in the code we've written so far! What we want to say here is that a month is 30 days, or alternately 4 weeks and that a year is 12 months, or 365 days, or 52 weeks. We have to define both equalities in multiple ways since they can be valid in different contexts. Time conversion, so near and dear to us, has some human-based inexactness baked in, and we must handle it correctly.

Okay then, how do we reconcile this split-personality equality? We handle the inexactness by processing equality Arrays specially and throw in a bit of convention enacted through our simple_align method - but we have to establish some new framework first.

units.rb   a units pipe dream
class NumericWithUnits

  attr_accessor :numeric, :unit, :original

  def initialize(numeric,unit,power=1,original=nil)
    @numeric, @unit, @original =
      numeric, units_hash(unit)**power, original
  end

end


class Units

  def Units.convert(numeric,unit_identifier)
    if (candidates = lookup(unit_identifier)).size == 0
      raise MissingUnitsException.new(unit_identifier.to_s)
    elsif !defining?
      if candidates.size > 1
        raise AmbiguousUnitsException.new(unit_identifier.to_s)
      else
        unit = candidates[0]
        NumericWithUnits.new(numeric,unit)
      end
    else
      if candidates.size == 1
        units = candidates
      else
        units = candidates.select { |candidate|
          @@defining == candidate.units_system.units_measure }
        units = candidates.select { |candidate|
          @@defining.derived[candidate.units_measure] } if units.size == 0
      end
      case units.size
        when 0 then
          raise MissingUnitsException.new(unit_identifier.to_s)
        when 1 then
          unit = units[0]
	  if unit.equals.kind_of? Array
	    element = unit.equals[0]
	    NumericWithUnits.new(numeric*element.numeric,element.unit,
	        1,numeric.unite(unit))
	  else
	    NumericWithUnits.new(numeric*unit.equals.numeric,unit.equals.unit,
	        1,numeric.unite(unit))
	  end
        else
          raise AmbiguousUnitsException.new(unit_identifier.to_s)
      end
    end
  end

end


class NumericWithUnits

  def promote_original
    @numeric, @unit = original.numeric, original.unit
  end

end


class UnitsUnit

  def normalize
    raise UnitsException.new("UnitUnits must have a name attribute") unless
      self.name
    self.name = self.name.to_s
    add_plural
    add_abbrevs
    add_equals
    equals.each { |n| n.promote_original } if equals.kind_of? Array
    self
  end

end


class NumericWithUnits

  def simple_align(target,all=true)
    factor = 1
    target_unit = UnitsHash.new
    unit.each do |tu,tv|
      su = target.unit.keys.select {|u|
          u.units_measure.equal? tu.units_measure }
      if tu.equals.kind_of? Array
	m = tu.equals.select{|u| u.unit[su[0]]}
	m = tu.equals.collect{|u|
	    u.align(1.unite(su[0]))}.compact if m.size == 0
	factor *= (m[0].numeric)**tv
	target_unit[su[0]] = tv
      else
        if su.size == 1
          e = su[0].equals
	  if e.kind_of? Array
	    m = e.select{|u| u.unit[tu]}
	    m = e.equals.collect{|u|
	        u.align(1.unite(su[0]))}.compact if m.size == 0
	    factor *= (1.0/m[0].numeric)**tv
	  else
	    factor *= (1.0*tu.equals.numeric/e.numeric)**tv
	  end
	  target_unit[su[0]] = tv
        else
          target_unit[tu] = tv
        end
      end
    end
    raise UnitsException.new("units mismatch") if
       all && (target.unit != target_unit)
    NumericWithUnits.new(numeric*factor,target_unit)
  end

end

The first realization is that because we have an inexactness, we need to retain it. If we work through it and leave it behind, we'll never know what the original intention was. So, we add a placeholder for the original intentions in a NumericWithUnits, and we tuck away the original values as we're defining units. Once we've defined the unit and are normalizing it, we add a step that says "if we have multiple interpretations, use the original intentions rather than the computed value" so we can figure out which one to use later when we have more context. We'll set everything up to make use of the intentions during conversion.

The heart of the mechanism is buried in alignment. What we do here is make it sensitive to units with arrays embeded into their notion of equality. When we have to align that involves a unit with inexact equality, we first check to see if a member of the equality contains a NumericWithUnits containing the unit we're after; if so we use it. If the unit isn't there, we then take each element of the equality and try to convert to the target unit - the first one we find wins and is rolled up into the conversion.

Here's where the subtle use of convention comes in: elements in the equality Array are tried in order. The first elements are preferred over the latter elements. This tightens up the inexactness and makes conversions predictable. Of course, there's nothing preventing us from using later elements in our conversions - we just have to convert through the Array explicitly.

But perhaps we've been to generic. Usually inexactness like this is caused by trying too hard to model everything with artificial rules rather than include the outlying richness of the real world. For instance, in business planning a generic month is typically considered to be 30 days long - but it's perfectly reasonable to ask how many days there are in August and use that as a basis for conversion. It's the facts about the real world that aren't reflected in our units system.

We can push on our units system slightly and include some of the real-world exceptions to the rules. For instance:

definitions.rb   a units pipe dream
Units.create :time do |m|
  m.system :months do |s|
    s.unit :name => :january, :equals => 31.days
    s.unit :name => :february, :equals => [28.days, 29.days]
    s.unit :name => :march, :equals => 31.days
    s.unit :name => :april, :equals => 30.days
    s.unit :name => :may, :equals => 31.days
    s.unit :name => :june, :equals => 30.days
    s.unit :name => :july, :equals => 31.days
    s.unit :name => :august, :equals => 31.days
    s.unit :name => :september, :equals => 30.days
    s.unit :name => :october, :equals => 31.days
    s.unit :name => :november, :equals => 30.days
    s.unit :name => :december, :equals => 31.days
  end
end

And spin a little bit of code into Object's method_missing method:

units.rb   a units pipe dream
class Object

  def method_missing(method,*args)
    begin
      s = method.to_s.split('_')
      if s.select{|e| e == 'per'}.size > 0
        per_ratio method, args
      elsif s.select{|e| e == 'in'}.size > 0
        per_ratio method.to_s.sub(/_in_/,'_per_').to_sym, args
      else
        convert_unit_value method, args
      end
    rescue Exception => exception
      method_missing_before_units method, args
    end
  end

end

This lets us do wonderful things like

Yeah, that's just the way I'd expect it to read. Except that fractional weeks aren't natural - when I ask for weeks, I want weeks and days. Let's add one of our formatters and tame this a little.

definitions.rb   a units pipe dream
Units.create :time do |m|
  m.system :base do |s|
    s.format :name => :weeks_and_days,
      :format => lambda { |u|
                          weeks = u.weeks.floor
                          days = (u-weeks).days.round
                          weeks == 0 ?
                             (days == 0 ? "#{weeks}" : "#{days}") :
                             "#{weeks} #{days}" }
  end
end

Now we can do

Beautiful! Except, darn it, using that format method just seems to get in our way. Sure, it's specific and we really do need to say what format we should use, but couldn't we just call it from the to_s method? Of course!

units.rb   a units pipe dream
class NumericWithUnits

  def to_s(format = nil)
    format == nil ? "#{numeric} #{unit.to_s(numeric)}" : self.format(format)
  end

end

That should do it.

I just love Ruby!

Okay. So now, let's get back to the speed of light.

definitions.rb   a units pipe dream
Units.derive :velocity, Units.length/Units.time do |m|
  m.system :metric do |s|
    s.unit :constant => true,
      :name => :speed_of_light,
      :no_plural => true,
      :equals => 299792458.meters/second,
      :abbrev => :c
  end
end

We see a few interesting things here, namely the use of :constant and :no_plural, but let's ignore those for a second. The big thing is that this definition will work, and should resolve our issue with the undefined forward references. Let's make sure.

Yep, Grace Hopper was right - about a foot per nanosecond.

One thing troubles me though. We've used underscores in the names of light years and astronomical units. I want to clean up this output.

units.rb   a units pipe dream
class UnitsHash

  def to_s(numeric = 1,use_abbrevs = false)
    if size == 1 &&
        (su = select {|k,v| v == 1}).size == 1 &&
        !use_abbrevs
      su = su.to_a[0][0]
      (numeric == 1 ? su.name : su.plural).gsub(/_/,' ')
    else
      abbrevs_to_s
    end
  end

end

Now let's go back and try that printing again.

No underscore, much nicer. Picky, perhaps. But it's the details that make it smooth. Looking at it symetrically however, what about underscores on the input side?

Error. Damn, it tried to parse light_years into converting light and years. This is a tough problem, one based in the ambiguity of human utterance. We want things both ways - in some cases the units should be separate, in the other we want the units divided. If we don't do it right, our units-users will get very confused. And what if it occurs during definition versus happening when units are being used? We need a plan.

Well, it looks like we have it right already during definition. If we look back to the definition of parsec in the length measure, that's defined as 3.262 light years, and it works just fine. The key there is that we don't decompose the units on underscores - we just take them verbatim as a unit identifier. Great and whew. Half our work is already working right and we don't have to try to handle some really hairy forward referencing issues. So let's look at the half not done.

Ah ha! There is hope! If we use unite, we can get around our issue by avoiding the parsing! And we could give unite an Array of units if we have multiples! Therefore, our plan is to convert these underscored units into the elements of an array during the parse before we hand them to the unite. Hmmm. But not just unite. We need to be underscore-sensitive anywhere we parse a method as units. It's time to pick a convention and do a little software dance.

Here's what we'll do. We'll look for occurences of the string '_and_'. If we see any, we'll automatically assume we have units with embeded underscores between them. If we don't see any, we'll try the string by itself. If we get back a MissingUnitsException, we'll assume the underscores are separating units and we'll parse out each of them. We'll be careful to only do our decomposition during unit use - we'll leave unit definition alone.

units.rb   a units pipe dream
class Object

  def convert_per_ratio(method)
    ps = method.to_s.split '_per_'
    raise UnitsException('invalid per method') if ps.size != 2
    value = (1.unite ps[1])/(1.unite ps[0])
    raise UnitsException.new("per ratio has units") if
      value.kind_of? NumericWithUnits
    value
  end

end


class Numeric

  def method_missing(method,*args)
    if Units.defining?
      reference = Units.make_forward_reference(method,Units.defining?)
      begin
        value = Units.convert self, method
        Units.release_forward_reference reference
        value
      rescue MissingUnitsException
        Units.hold_forward_reference
      rescue UnitsException => exception
        units_problem("definition",exception,method,args)
      rescue Exception => exception
        method_missing_before_units(method,args)
      end
    else
      begin
        ps = method.to_s.split '_per_'
        case ps.size
        when 1 then unite method
        when 2 then unite(ps[0])/(1.unite(ps[1]))
        else raise UnitsException('invalid per method')
        end
      rescue UnitsException => exception
        units_problem("usage",exception,method,args)
      rescue Exception
        method_missing_before_units(method,*args)
      end
    end
  end

  def unite(unit=nil,power=1,measure=nil)
    if !unit
      self
    else
      if (unit.kind_of? Array)
        units = UnitsHash.new
        unit.each {|u| units.merge! 1.unite(u)}
        unit = units
      elsif (unit.kind_of? String)||(unit.kind_of? Symbol)
        s = unit.to_s
        as = s.split('_and_')
        if as.size == 1
          units = Units.lookup(as[0])
          case units.size
          when 1 then unit = units[0]
          when 0
            as = as[0].split('_')
            units_problem("usage",
                          AmbiguousUnitsException.new(unit),
                          :unit=,unit) if as.size == 0
            unite(as,power,measure).unit
          else
            units_problem("usage",
                          AmbiguousUnitsException.new(unit),
                          :unit=,unit)
          end
        else
          unite(as,power,measure).unit
        end
      elsif unit.kind_of? NumericWithUnits
        unit = unit.unit
      end
      NumericWithUnits.new(self,unit,power)
    end
  end

end


class NumericWithUnits

  def method_missing(method,*args)
    begin
      s = method.to_s
      ms = s.split '_'
      if ms[0] == 'to'
        convert! s.gsub(/^to_/,"")
      elsif ms[0] == 'in'
        convert s.gsub(/^in_/,"")
      elsif ms.select{|e| e == 'per'}.size > 0
        convert_per method
      else
        convert s
      end
    rescue Exception
      value = numeric.send(method,*args)
      if (value.kind_of? String)
        "#{value} #{unit.to_s(numeric)}"
      else
        NumericWithUnits.new(value,unit)
      end
    end
  end

  def convert_per method
    ps = method.to_s.split '_per_'
    raise UnitsException.new('invalid per method') if ps.size !=  2
    numerator = 1.unite(ps[0])
    numerator_units = Set.new numerator.unit.keys
    denominator = 1.unite(ps[1])
    denominator_units = Set.new denominator.unit.keys
    positives = unit.keys.collect{|k| unit[k] > 0 ? k : nil}.compact!
    negatives = unit.keys.collect{|k| unit[k] < 0 ? k : nil}.compact!
    numerator_positives =
      Set.new 1.unite(positives).align(numerator,false).unit.keys
    numerator_negatives =
      Set.new 1.unite(negatives).align(numerator,false).unit.keys
    denominator_positives =
      Set.new 1.unite(positives).align(denominator,false).unit.keys
    denominator_negatives =
      Set.new 1.unite(negatives).align(denominator,false).unit.keys
    if (numerator_units == numerator_positives) &&
       (denominator_units == denominator_negatives)
      convert(ps[0]+'_and_'+ps[1])
    elsif (numerator_units == numerator_negatives) &&
       (denominator_units == denominator_positives)
      convert(ps[0]+'_and_'+ps[1])**-1
    else
      raise UnitsException.new('invalid per units')
    end
  end

end

Apart from determining if we need special treatment for the per conversion, we've pulled all of the heavy lifting into the unite method. Now we can say things like

and they just work.

Okay, let's quick jump back to the :constant and :no_plural we used to define the speed of light. We skipped over them because they really don't mean much - the :constant is just for information purposes, and the :no_plural is just a cue for default formating. We're going to leave the :constant out of everything - remember, since a UnitsUnit is a kind of MethodicHash, it's just getting added as a hashed value with no other overhead. We'll add just a little bit of code to take care of the :no_plural case.

units.rb   a units pipe dream
class UnitsUnit

  def add_plural
    self.plural =
      (self.no_plural == true) ? self.name :
      (plural = self.plural) ? plural.to_s : self.name+'s'
  end

end

That's it. If it's set to no plural, we make the plural the same as the singular name. This allows fictitios statements to be made, like

and keep the unit name singular even when a plural would have been used. The trick is that we are using the plural, but it's the same as the singular form.

Suddenly, Rousted From the Dream

A good friend of mine recently mentioned the need for a Units Framework - and I couldn't help but committing the gem to the waking hours before I had a chance to complete the work. However, since the scalar are complete, now's a good time. More is to come, but for now I've been rousted.

I will be back to bed shortly, hopefully to pick up where I've left off...