"Stop the pigeon! Stop that pigeon now!"
Sheriff of Nottingham, Sherwood Forest, The Adventures of Robin Hood


"That don't impress me much!"
Shania Twain, Come on over


"There is a theory which states that if ever anyone discovers exactly what the Universe is for and why it is here, it will instantly disappear and be replaced by something even more bizarre and inexplicable. There is another theory which states that this has already happened"
Douglas Adams


"Get the cheese to sickbay"
B'Elanna Torres, Starship Voyager


"What did y'all order a dead guy for?"
Jayne Cobb, Firefly


"Excuse me, but I am in the middle of fifteen things, all of them annoying"
Susan Ivanova, Babylon 5


place
Quanities and Units of Measure
Home > SoftwareDesign > General
Of quantities, units and conversions building on patterns described in print by Martin Fowler...

In his book, Analysis Patterns: Reusable Object Models, Martin Fowler documents a number of useful patterns for storing quantities such as a length, weight, speed, pressure or temperature. While Martin's examples and arguments are predominantly drawn from the medical profession, these patterns are generally applicable wherever values can be expressed in different units of measure. The following examines the implementation of some of these patterns through different examples, adding detail, making additional observations, and discussing some alternatives.

Quantities as Attributes

Of course, the simplest way to store a quantity is to represent that quantity as an attribute of a class. For example, a real estate application that records the living space as floor area of a residence could represent this as a suitably-named integer attribute of a Residence class. Similarly, it might store the amount of land on which a residence stands (lot size) in another attribute of the same class (see figure 1).

class Residence {
     ...
     int floorArea;
     int lotSize;
     ...
}

Figure 1: Quantities as attributes (member fields, instance variables) of a class

As Martin Fowler points out, this approach can cause problems because the attributes contain no indication of the units that the quantities were measured in. The floor area and lot size are likely to be expressed in metres squared in Europe but in square feet and acres respectively in the USA. Without any explicit indication of what a quantity is measured in, programmers can make bad assumptions, as history has shown

One very simple solution is to expand the name of the attribute to include the units of measure and define accessor methods for each unit of measure we are interested in. Without the extra accessor methods, different users of the class might need to build code to convert from one unit to the other. We could end up with duplicated unit conversion code and a greater chance of errors in that code. Encapsulating the conversion logic in the Residence class eliminates this problem.

class Residence {
     ...
     int floorAreaInSqFt;
     int lotSizeInAcres;
     ...
     int getFloorAreaInSqFt() { return floorAreaInSqFt; }
     void setFloorAreaInSqFt( int floorAreaInSqFt ) { this.floorAreaInSqFt = floorAreaInSqFt; }
     ...
     int getFloorAreaInMetresSquared() {
         // convert floorAreaInSqFt into metres squared and return result ...
     }
     void setFloorAreaInMetresSquared( int floorAreaInmetresSquared) {
         //  convert parameter value into square feet and store in floorAreaInSqFt ...
     }
     ...
}

Figure 2: Units as part of attribute names and accessor methods

For very simple cases, expanding names and adding extra accessor methods might be sufficient. However, it quickly becomes laborious in a problem domain that has numerous concepts with a number of measured quantities. A system for recording results at a major athletics games such as the Olympics is one example:

  • Lengths need recording for distances jumped and thrown
  • Times need recording for distances run, swam, and cycled
  • Weights need to be recorded in weight lifting events
  • Points need to be recorded in judged (e.g. diving), combined (e.g. decathlon) or accuracy events (e.g. shooting)
In addition to the actual quantities achieved in each event,  qualifying heights, distances, times or weights must be jumped, thrown, run or lifted to enter an event. Competitors have personal and season best quantities, and each event has a best distance/length/time/weight/score for the venue, for these specific games (Olympics, Commonwealth, etc), and world record distance.

multiple distances as ints

Figure 3: Classes for a very simple field event module

Field events are all about distances, either jumped or thrown. Figure 3 shows a set of classes for a simplistic module for field event results:

  • The FieldEventParticipation class describes the participation of a competitor in a particular event. Objects of this class hold the season and personal best distances for its related competitor before the start of the event. During the event, the distances the competitor achieves are added.

  • Objects of the FieldEvent class hold distances for qualification, the venue record, the games record and the world record. With these quantities recorded, we can easily define operations that calculate the medal winners, determine if records-have been broken, identify new personal best achievements, and so on.

Adding extra accessor methods for each distance attribute and the ability to convert between metres, centimetres, millimetres, yards, feet, and inches to both of the pink classes in the model is tedious and duplicates unit conversion code in both classes.

The duplicated code problem could be addressed by creating a 'utility class' with a series of class-level (static) methods that convert between the different units that an event might be recorded in. After, sending the designer of such a scheme to the bottom of the object-oriented design class, we look for a more elegant answer.

Note 1: the colours and stereotypes in figure 3 come from Peter Coad's 'modeling in color' technique.

Note 2: the Number type used for the attributes is a place-holder for a suitable decimal type of the programming language being used.

Note 3: at this level of abstraction, I use public attributes in UML classes to represent Java bean/C# style properties

Quantity and Unit Classes

The solution, as it so often is in object-oriented software, is more encapsulation. Martin Fowler suggests modeling quantities as shown in figure 4.

quantity and units

Figure 4: Quantity and Units with ConversionRatio

In figure 4, the Quantity class encapsulates the amount and units of a quantity and takes on the responsibility of converting a quantity into one with different units. To perform this conversion, an object of the Quantity class looks for an object of the ConversionRatio class that links its Unit object to the desired Unit object. For example, if a Quantity object is linked to the metre object of the Unit class, and we need the quantity in inches, the convertTo() method needs to locate an object of the ConversionRatio class that is associated with both the metre and the inch object of the Unit class.

If no suitable ConversionRatio object exists, it may be possible to combine multiple ConversionRatio objects to provide a sequence of multiplications that will convert between the two units. For example, if when converting a quantity from centimetres to feet, the convertTo() operation cannot find an object of the of the ConversionRatio class with links to both centimetres and feet, it may be able to find one that links from centimetres to metres and another that links metres to feet. This requires some level of sophistication in the code doing the look-up.

A simpler alternative is for objects of the Unit class to each contain the multiplier value needed to convert to a specific unit. This specific unit could be the relevant SI base unit; metre for distance, kilogram for weight, second for time, and so on. Now, instead of the overhead of finding the relevant ConversionRatio object or sequence of ConversionRatio objects,  converting a quantity from one set of units to another is three simple steps:

  1. check that the current unit and desired unit both have the same SI base units
  2. multiply the amount of the quantity by the multiplier of its unit to get the amount in the relevant SI base units.
  3. divide the new amount by the multiplier of the desired unit to create the quantity in the desired units.

Note 1: It is invalid to convert between units with different SI base units because you cannot convert between two different types of measurement e.g. convert between a length and a weight.

Note 2: the multiplier must never be zero to avoid division by zero errors. In fact, the multiplier must be a positive number.

To enable us to check that two quantities share the same SI base units, we simply link each object of the Unit class to the object of the Unit class representing the relevant SI base unit (see figure 5).

In this alternative to the model in figure 4, we have completely removed the need for the ConversionRatio class; a simpler model that still does the job (see figure 5). Peter Coad would approve!

From Ratio to Linear Function

While a simple ratio can be used to convert between many units of measure, ( e.g. inches to millimetres, millimetres to metres, etc) a more sophisticated function is required to convert between items like Celsius and Fahrenheit. To convert from Celsius to Fahrenheit, you multiply by 1.8 and then add 32.  In mathematical terms, converting between Celsius and Fahrenheit requires a function of the form y=mx+c, known as a linear function. To go the other way, from Fahrenheit to Celsius you subtract 32 and divide the result by 1.8. This requires rearranging the formula into x=(y-c)/m. Therefore, if in addition to the multiplier, m, that we used for converting by a simple ratio, we also keep a constant, c,  in the Unit class we can convert between units like Celsius and Fahrenheit that require a linear function. Our model now looks something like figure 5.

alternative quantity
Figure 5: Alternative Quantity/Unit model

If, in addition to linear functions, we need to support different kinds of function such as logarithmic or exponential functions, a strategy pattern starts to look an attractive.

Note: my first major software development project involved plenty of linear function conversions from raw measurements of temperatures and pressures into the units appropriate for the control room displays of an oil-fired power station.

Maths with Quantities

What happens if we want to add or subtract quantities? The first thing to do is check if both quantities are in the same units. If they are then we simply add or subtract the values in the amount attributes of each Quantity object. If the units are different, we need to convert either one or both of the quantities so that we do have two quantities with the same units. So that we do not have write the code for the checks and conversions everywhere we want to add or subtract quantities, we make it the responsibility of the Quantity class, defining operations to add or subtract another Quantity object (see figure 6).

But what units should the result of one of these operations be in? If I ask what is 6 feet plus 6 inches, the natural answer is 6 and a half feet, not 78 inches, or 2.1667 yards. Therefore, we could make results of our new addition and subtraction operations the larger of the two units but that means dong extra work to determine which of the two is the larger. An easier alternative is to always express the answer in the units of the object that we invoke the operation on. In other words, if we say A.add(B)  then we express the answer in A's units but if we say B.add(A) we express the answer in B's units. This is a more flexible for addition than subtraction because the answer is the same whether we add A to B or B to A. Unfortunately, that is not the case for subtraction. Despite this restriction, it is simpler if we apply the same rule for subtraction and return the result in the units of the object that we invoke the operation on. Therefore, A.subtract(B) returns a result in the units of A.

What about multiplication and division? It is nonsense to talk about multiplying or dividing one quantity by another; 6 pounds multiplied by 3 ounces is meaningless. It only makes sense to talk about multiplying or dividing a quantity by a number. For example, 3 ounces multiplied by 6 is 18 ounces.  There is only one quantity involved so it is easiest to express the result of the operation in the same units as that quantity. However, this might look a little unnatural because we naturally change units when amounts become too large or too small. For example, four centimetres multiplied by a hundred is most naturally expressed as four metres, not four hundred centimetres. We could add logic to the Quantity class to select the most appropriate units for the result of a maths operations but for most situations this is likely to be straying into the realm of over-engineering.

We can define comparison operations for the Quantity class too. These are simpler to define because they return either a boolean or an indication of greater, less than or equal to; no units to choose for the result. Equals can be a problem for amounts where rounding takes place during conversion between units or arithmetic. Therefore, in addition to the basic equals operation that compares values exactly, the Quantity class typically needs an  isWithin() operation that declares two quantities as equal if they are within a specified tolerance amount of each other.

Non-decimal Quantities

None of the models we have seen so far fully caters for non-metric systems because none caters for expressing quantities in non-decimal format such as 8 hours, 6 minutes and 14 seconds, or  6 miles, 12 yards, 5 feet and 6 inches, or 4 pounds 2 ounces. To do this, each value would need to comprise of a sequence of one or more Quantity objects with additional logic to manage the correct use of Unit objects. In addition, the basic mathematical operations become much more complicated because they need to deal with this sequence of Quantity objects and non-decimal values. Now our model needs to look something like figure 6:

Non-decimal quantities

Figure 6: Non-decimal Quantities

In figure 6, a quantity now may be made up of a sequence of Quantity objects where each subsequent object in the sequence is in a sub-unit of the previous Quantity's units. The Unit class defines a similarly linked sequence of Unit objects to constrain the immediate sub-unit and super-unit for a particular Unit object. For example, the mile has an immediate sub-unit of yard. This in turn, has an immediate super-unit of mile and an immediate sub-unit of foot. Additionally, each Unit object knows how many of its sub-units there are in one of its own units and how many of itself there are in one of its super-units. This additional knowledge is needed to perform the basic maths operations; to be able to add 2 feet and 11 inches to 2 yards, 2 feet and 9 inches, we need to know that there are 12 inches in a foot and 3 feet in a yard,

Note: these additional values are the same as m would be in y=mx+c if we were converting values between sub-units x and units y; c is obviously always zero in these cases.

Again, for most circumstances, fully supporting non-decimal quantities is likely to be over-engineering the solution. There are very good reasons why we use decimal numbers and why most of the world has officially adopted the metric system albeit with some popular remnants of old units (UK pubs and bars still sell beer in pints, not liters). Decimal numbers and metric quantities are so much easier to work with in most software situations.

Compound Units

Some units of measure are comprised of a number of others.
  • Speed is measured in distance over time: metres per second, miles per hour, and on on.
  • Acceleration is measured as the change in speed per second: metres per second per second or, in other words, metres per second squared.
  • Pressure is measured in wieght, or more accurately, mass over an area: pounds per square inch, kilograms per metre squared, etc.
  • and so on.

In Analysis Patterns, Martin Fowler proposes two models that explicitly support compound units.  Figure 7 shows his preferred shape but with additional link to the relevant SI base unit introduced in figure  5

Compound Units

Figure 7: Compound units modeled explicitly

The Unit class becomes an abstract class with two subclasses:

  • Atomic Unit that represents fundamental units like inches, metres, pounds, kilograms, etc
  • Compound Unit that represents combinations of AtomicUnits
CompoundUnit objects keep two collections of AtomicUnit objects, direct and inverse that work as follows:
  • A Compound Unit object representing speed measured in miles per hour has the miles atomic unit in its direct collection and the hours atomic unit in its inverse collection.

  • A Compound Unit object representing pressure measured in kilograms per metre squared has the kilogram atomic unit in its direct collection and two references to the metre atomic unit in its inverse collection.
Conversion between compound units requires a more complex process:
  1. check that the current unit and desired unit both have the same SI base unit
  2. convert the quantity to the SI base units by
    1. multiplying the amount of the quantity by the multiplier of each of the atomic units referenced in the direct collection of the quantity's compound unit.
    2. then divide by the multiplier of each of the atomic units referenced in the inverse collection of the quantity's compound unit.
  3. convert from the SI base unit to the desired unit by
    1. dividing the amount of the quantity by the multiplier of each of the atomic units referenced in the direct collection of the desired compound unit.
    2. then multiply by the multiplier of each of the atomic units referenced in the inverse collection of the desired compound unit.

Note 1: It is still invalid to convert between units with different SI base units because you cannot convert between two different types of measurement e.g. convert between an acceleration and a pressure.

Note 2: the multiplier must still never be zero to avoid division by zero errors. In fact, the multiplier must be a positive number.

Note 3: a more sophisticated algorithm ignores units that appear in the same collections of both the current and desired units. For example when converting between miles per hour and kilometres per hour, the hours can be ignored because the division for the hour when converting to the SI base unit is cancelled by the multiplication for the hour when converting to the desired units.

Note 4: It is still valid to support a linear function instead of a straight ratio as described in figure 5 or use a strategy pattern instead.

Reality Check

Very few software systems actually call for the conversion of values between compound units or full support for non-decimal amounts. Providing such capabilities when they are not needed is a waste of time and money. In over twenty years, none of the business or industrial projects that I have worked required either of these capabilities. I included the patterns here for interest and completeness more than for their typical real-world usefulness.

Quantity on the Outside, Values on the Inside

Armed with our Quantity and Unit pattern, we can rework our field event module's classes. For field events, we are only interested in distance, so our Quantity can be expressed more specifically as a class called Distance. In addition, our units are all some sort of distance, so all have the same SI base unit, the metre. This means we can simplify the Unit class further. Instead of each instance holding a reference to their related SI base unit object, we can reference the metre object from a class-level (static in C++, Java, and C#) variable. The checks for quantities being related to the same SI base unit object in the conversion, comparison and arithmetic operations are redundant. Finally, all the units convert to metres via a straight forward ratio; full linear function support in unit conversions is not needed. Figure 7 shows the simpler, more specific model.

Distance
Figure 7: Application of Quantity pattern produces Distance and Unit classes

This sort of tweaking is typical when applying a pattern. Patterns should not be applied blindly without thought. On the other hand, a pattern should only be tweaked by someone that fully understands that pattern so that the modifications do not undermine the whole point of using it.

Now that we have our Distance and Unit classes we simply replace all the Number attributes in the model from figure 3 with attributes of type Distance. Job  done! Time for a cup of tea! Well, maybe. It is true that if we want to retain the knowledge of what units each quantity was measured in, then we need to use attributes of type Distance because each one has its own associated Unit object. Martin Fowler argues that this is exactly what is required for a medical software system. However, as we have seen, it is more work to do comparisons and maths operations if the values involved are in different units. For our simplistic field event system, we are not concerned with remembering that the official from USA measured in feet and inches and the French official in centimetres. We have decided we want everything converted to and stored in metres, the SI base unit of length.

atributes of type Distance

Figure 8: Attributes of type Distance

In this case, we can leave the private attributes as type Number but insist that the public operations of the classes be defined in terms of quantities (see figure 9). Now:

  • Calculations and comparisons within our classes do not need to be concerned about unit conversions.
  • Every quantity supplied to an object of our classes is converted to our internal unit of choice, the metre.
  • All operations returning quantities, return a Distance object linked to the metre object of the Unit class.
To satisfy this last item, we could hard code a look up of the metre object of the Unit class. Instead, we hold a reference from the FieldEvent class to the Unit class object representing metres (static level reference in C++, Java, and C#).

inside out

Figure 9: Number on the inside, Quantity from the outside

To summarize:
  • Where it is important to remember what units a quantity was actually measured, use attributes of type Quantity.
  • Where it is important that all amounts in within an object be in the same unit, convert all quantities to the specific unit, hold the amounts as simple values and define all public operations in terms of Quantity objects.
  • Remember not to blindly apply a pattern but look to see if the problem domain allows the general pattern to be simplified.

Different Kinds of Measurement

Up until this point, we have only modeled field events because these all involved recording distances, either thrown or jumped. Other types of event are measured in time, points, or weight. These are still quantities but they are not distances. We could support these events by defining specific quantity and unit classes for time, for points, and for weight. Obviously, these pairs of classes would be very similar. It is tempting to use inheritance or generics to express this commonality, we quickly become concerned with issues of co and contra-variance. While a solution based on inheritance or generics may provide some compile time type safety, a simpler approach is to return to a more general implementation of the quantity pattern, make our FieldEvent and FieldEventParticipation classes more general, and define a few more Unit objects (see figure 10).

More general model

Figure 10:  More general model to support more types of athletics events

This does introduce two problems:

  1. The static or class-level link reference from AthleticsEvent to Unit is not suitable. The new events supported need to be measured in completely different units. No single unit can be used to capture distance, time, points and weight.

  2. Nothing in our model prevents recording quantities for an event in inappropriate units. For example, nothing prevents a weight being recorded for the 100m sprint event.
The first problem is simply solved by changing the association between AthleticsEvent to Unit to be an instance level association (non-static). The required Unit object is either passed as a parameter to a constructor of the AthleticsEvent class (see figure 10) or initialised by an appropriate setter method.

The second problem can be solved by adding a check to each of the setter methods that compares the SI Base Unit of the AthleticEvent object's associated Unit object with that of the Unit object of the Quantity object passed as the parameter to the method. Only if they are the same can the parameter by accepted. The check, of course, should be factored out into a private method that can be called by each setter method.

Money, Money, Money

Another kind of quantity is money. The units of measure for money are currencies. The big difference between money and the quantities consider so far is that the conversion ratio or exchange rate between currencies moves. The conversion ratio between inches and feet is always twelve. The value of exchange rates fluctuate, soar and dive. Different exchange rates apply according to circumstances. Are you buying or selling? How much are you buying or selling? Are you buying or selling now or for in the future? How much money is available at a particular rate.

Because the rate moves, and different rates may be applied, simply encapsulating 'the rate' inside the unit class does not work. Either we need to supply the relevant exchange rate to the currency class when we convert between currencies or we supply the currency to something else that does the conversion.


Money, Currencies and Exchange Rates

Figure 11: Money, Currency and Exchange Rates

Figure 10 shows some of the changes typically needed in serious enterprise software involving exchange rates. Three new classes have appeared in comparison to figure 5:

ExchangeRate objects hold the multiplier needed to convert a MonetaryValue from one Currency to another. It is similar to the ConversionRatio in figure 4 but there are subtle differences:
  • Currency (unit) objects cannot hold references to a specific ExchangeRate object because the rate changes over time outside of its control. The only way for a Currency object to know the right ExchangeRate object to use is to be told at the time of the conversion and the rate might be different for different conversions, due to amount or source of the rates, etc. The only way this would work is if duplicate Currency objects for the same currency were created, possibly one for each MonetaryValue. Not an elegant solution for most situations I have worked in.

  • Exchange rates are not generally symmetrical. For example, the rate going from US dollars to British pounds is rarely the same in practice as the rate for going from British pounds to US dollars.

  • An exchange rate for the desired conversion may not be available directly but a combination of exchange rates between different currencies may produce a sequence of conversions that will satisfy the request. Often such a sequence will be available through a well known currency such as US dollars, British pounds, or Euros. However, while this might seem similar to the use of a SI base unit, the change over time in the rate to and from this well known currency complicates things.

Therefore, it is typically cleaner for the ExchangeRate class to be responsible for converting one MonetaryValue object into another instead of MonetaryValue (quantity) class.

Additionally, in some cases, an extra attribute may be required that holds the amount of funds still available at that rate. This can get complicated if users of the class need to check if a certain amount is available before confirming the conversion. We essentially have a typical reservation or stock-on-hand problem; someone else could reduce the amount available after we have checked but before we have confirmed a conversion.

ExchangeRateSchedule objects represent a collection of exchange rates from a particular source that are applicable for a particular period of time. ExchangeRateSchedule objects are often responsible for finding the best ExchangeRate object for a requested conversion. Depending on the situation, this might be constrained to the collection of rates that it knows about, or it may be able to derive a new ExchangeRate object from a  combination. Where limited funds are available for each rate, deriving new ExchangeRate objects becomes even more complicated.

ExchangeRateBoard objects represent a regular source of exchange rates. It might represent a list from a financial newspaper, the board displayed in bank branches or shops that accept multiple currencies, etc. Depending on the circumstances, the board is responsible for producing either a schedule of ExchangeRate objects, or specific ExchangeRate objects on demand. In the latter case, the ExchangeRateSchedule class may not be needed and the ExchangeRate class takes on the responsibility of tracking the effective interval for the its rate.

A general-purpose exchange rate model that covers all currency conversion scenarios requires more than that shown in figure 11 but is typically overkill for most situations. The model in figure 11 does show how quickly the simple model in figure 5 becomes more complicated when the conversion ratio between units changes unpredictably over time.