Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

TL;DR - I'm looking for the "optimal" data structure to define units within a unit of measure.


A Unit of measure is essentially a value (or quantity) associated with a unit. SI Units have seven bases or dimensions. Namely: length, mass, time, electric current, temperature, amount of substance (moles), and luminous intensity.

This would be straightforward enough, but there are a number of derived units as well as rates that we frequently use. An example combined unit would be the Newton: kg * m / s^2 and an example rate would be tons / hr.

We have an application that relies heavily upon implied units. We'll embed the units within the variable or column name. But this creates problems when we need to specify a unit of measure with different units. Yes, we can convert the values at input and display but this generates a lot of overhead code that we'd like to encapsulate within its own class.

There are a number of solutions out on codeplex and other collaborative environments. The licensing for the projects is agreeable but the project itself usually ends up being too lightweight or too heavy. We're chasing our own unicorn of "just right."

Ideally, I could define a new unit of measure using something like this:

UOM myUom1 = new UOM(10, volts);
UOM myUom2 = new UOM(43.2, Newtons);

Of course, we use a mix of Imperial and SI units based upon our clients' needs.

We also need to keep this structure of units synchronized with a future database table so we can provide the same degree of consistency within our data too.


What's the best way of defining the units, derived units, and rates that we need to use to create our unit of measure class? I could see using one or more enums, but that could be frustrating for other developers. A single enum would be huge with 200+ entries whereas multiple enums could be confusing based upon SI vs Imperial units and additional breakdown based upon categorization of the unit itself.

Enum examples showing some of my concerns:

myUnits.Volt
myUnits.Newton
myUnits.meter

SIUnit.meter
ImpUnit.foot DrvdUnit.Newton
DrvdUnitSI.Newton
DrvdUnitImp.FtLbs

Our set of units in use is pretty well defined and it's a finite space. We do need the ability to expand and add new derived units or rates when we have client demand for them. The project is in C# although I think the broader design aspects are applicable to multiple languages.


Update 1: One of the libraries I looked at allows for free-form input of units via string. Their UOM class then parsed the string and slotted things accordingly. The challenge with this approach is that it forces the developer to think and remember what the correct string formats are. And I run the risk of a runtime error / exception if we don't add additional checks within the code to validate the strings being passed in the constructor.

Another library essentially created too many classes that the developer would have to work with. Along with an equivalent UOM it provided a DerivedUnit and RateUnit and so on. Essentially, the code was overly complex for the problems we're solving. That library would essentially allow any:any combinations (which is legitimate in the units world) but we're happy to scope our issue (simplify our code) by not allowing every possible combination.

Other libraries were ridiculously simple and hadn't even considered operator overloading for example.

In addition, I'm not as worried about attempts at incorrect conversions (for example: volts to meters). Devs are the only ones who will access at this level at this point and we don't necessarily need to protect against those types of mistakes.

share|improve this question
    
Could you explain in what ways don't the libraries you found fit your needs? –  svick May 8 '13 at 13:29
1  
See also stackoverflow.com/q/348853/240613 –  MainMa May 8 '13 at 13:42
1  
@MainMa - thanks for that link. We don't need to do dimensional analysis as our problem space is small enough that we would just declare the allowed conversions. It will be a slog to generate but that's a one time cost. –  GlenH7 May 8 '13 at 13:47
1  
Can you explain what kind of conversions you need? Is it only scaling conversion (e.g. meter to centimeter) or also cross-dimensional conversions (e.g. mass to force)? –  Bart van Ingen Schenau May 8 '13 at 14:58
    
@BartvanIngenSchenau - we have either simple scaling conversions or systems conversion (SI -> Imperial). We do not have a general purpose need for dimensional analysis / conversion (e.g. mass * acceleration => force). I can't think of any, but if we had a dimensional analysis use case then we'd be fine with "hard coding" that into the system. –  GlenH7 May 8 '13 at 15:06
show 2 more comments

7 Answers

The Boost libraries for C++ include an article on dimensional analysis that presents a sample implementation of handling units of measure.

To summarize: Units of measurement are represented as vectors, with each element of the vector representing a fundamental dimension:

typedef int dimension[7]; // m  l  t  ...
dimension const mass      = {1, 0, 0, 0, 0, 0, 0};
dimension const length    = {0, 1, 0, 0, 0, 0, 0};
dimension const time      = {0, 0, 1, 0, 0, 0, 0};

Derived units are combinations of these. For example, force (mass * distance / time^2) would be represented as

dimension const force  = {1, 1, -2, 0, 0, 0, 0};

Imperial versus SI units could be handled by adding a conversion factor.

This implementation relies on C++-specific techniques (using template metaprogramming to easily turn different units of measurement into different compile-time types), but the concepts should transfer to other programming languages.

share|improve this answer
    
So all of the derived units are equivalent to C++ consts? I presume they're wrapped into a namespace to avoid polluting things? –  GlenH7 May 8 '13 at 13:42
1  
@GlenH7 - This gets into the template metaprogramming stuff. They're actually represented as separate types (e.g., mpl::vector_c<int,1,0,0,0,0,0,0>) instead of consts; the article presents the consts approach first by way of explanation (and I probably didn't explain that well). Using consts would work as an alternative (you'd lose some compile-time type safety). Using a namespace to avoid name pollution is certainly an option. –  Josh Kelley May 8 '13 at 13:46
add comment

Based on the fact that all required conversions are scaling conversions (except if you have to support temperature conversions. Calculations where the conversion involves an offset are significantly more complex), I would design my 'unit of measure' system like this:

  • A class unit containing a scaling factor, a string for the unit's textual representation and a reference that the unit scales to. The textual representation is there for display purposes and the reference to the base unit to know which unit the result is in when doing math on values with different units.

    For each supported unit, a static instance of the unit class is provided.

  • A class UOM containing a value and a reference to the value's unit. The UOM class provides overloaded operators for adding/subtracting another UOM and for multiplying/dividing with a dimensionless value.

    If addition/subtraction is performed on two UOM with the same unit, it is performed directly. Otherwise both values are converted to their respective base units and the added/subtracted. The result is reported as being in the base unit.

Usage would be like

unit volts = new unit(1, "V"); // base-unit is self
unit Newtons = new unit(1, "N"); // base-unit is self
unit kiloNewtons = new unit(1000, "kN", Newtons);
//...
UOM myUom1 = new UOM(10, volts);
UOM myUom2 = new UOM(43.2, kiloNewtons);

As operations on incompatible units are not considered an issue, I have not tried to make the design type-safe in that respect. It is possible to add a runtime check by verifying that two units refer to the same base unit.

share|improve this answer
    
Since you mentioned temperature: what is 95F - 85F? What is 20C - 15C? In both examples, both UOMs would have the same unit. Would the subtractions be performed directly? –  Matt Fenwick May 8 '13 at 17:46
    
@MattFenwick: The results would be respectively 10 F and 5 C. The calculations are performed directly if possible, to avoid unneeded conversions. It would be fairly trivial to add unit-conversion methods to UOM, but for Celsius-Fahrenheit conversion, the unit class would have to be extended with the possibility of an offset in addition to a scaling factor. –  Bart van Ingen Schenau May 9 '13 at 11:21
    
But 95F - 85F != 10F. –  Matt Fenwick May 9 '13 at 12:03
1  
@MattFenwick: Please enlighten me. How cold does it get if you lower a temperature of 95F by 85F? To my knowledge, Fahrenheit is still a linear scale. –  Bart van Ingen Schenau May 9 '13 at 12:21
2  
Let's do the Celsius example because it's easier to convert to Kelvin: if we say 20C - 15C = 5C, then we're saying 293.15K - 288.15K = 278.15K, which is clearly wrong. –  Matt Fenwick May 9 '13 at 12:52
show 2 more comments

I just released Units.NET on Github and on NuGet.

It gives you all the common units and conversions. It is light-weight, unit tested and supports PCL.

Towards your question:

  • This is on the lighter end of the implementations. Focus is to aid in unambiguous representation, conversion and construction of units of measurement.
  • There is no equation solver, it does not automatically derive new units from calculations.
  • One big enum for defining units.
  • UnitConverter class for converting dynamically between units.
  • Immutable data structures for converting explicitly between units.
  • Overloaded operators for simple arithmetic.
  • Extending to new units and conversions is a matter of adding a new enum for dynamic conversion and adding a unit of measurement class, like Length, for defining explicit conversion properties and operator overloading.

I have yet to see the holy grail of solutions in this domain. As you state, it can easily become way too complex or too verbose to work with. Sometimes, it's best to keep things simple and for my needs this approach has proven sufficient.

Explicit conversion

Length meter = Length.FromMeters(1);
double cm = meter.Centimeters; // 100
double yards = meter.Yards; // 1.09361
double feet = meter.Feet; // 3.28084
double inches = meter.Inches; // 39.3701

Pressure p = Pressure.FromPascal(1);
double kpa = p.KiloPascals; // 1000
double bar = p.Bars; // 1 × 10-5
double atm = p.Atmosphere; // 9.86923267 × 10-6
double psi = p.Psi; // 1.45037738 × 10-4

Dynamic conversion

// Explicitly
double m = UnitConverter.Convert(1, Unit.Kilometer, Unit.Meter); // 1000
double mi = UnitConverter.Convert(1, Unit.Kilometer, Unit.Mile); // 0.621371
double yds = UnitConverter.Convert(1, Unit.Meter, Unit.Yard); // 1.09361

// Or implicitly.
UnitValue val = GetUnknownValueAndUnit();

// Returns false if conversion was not possible.
double cm;
val.TryConvert(LengthUnit.Centimeter, out cm);
share|improve this answer
add comment

If you can pull switching to F# instead using C#, F# has a units of measure system (implemented using metadata on values) that looks like it'll fit what you're trying to do:

http://en.wikibooks.org/wiki/F_Sharp_Programming/Units_of_Measure

Notably:

// Additionally, we can define types measures which are derived from existing measures as well:

[<Measure>] type m                  (* meter *)
[<Measure>] type s                  (* second *)
[<Measure>] type kg                 (* kilogram *)
[<Measure>] type N = (kg * m)/(s^2) (* Newtons *)
[<Measure>] type Pa = N/(m^2)       (* Pascals *)
share|improve this answer
    
Good suggestion, and we've considered it. I don't believe F# gives us the ability to control how the units end up being displayed on output though. –  GlenH7 May 8 '13 at 13:41
1  
@GlenH7 I believe you're right: Important: Units of measure look like a data type, but they aren't. .NET's type system does not support the behaviors that units of measure have, such as being able to square, divide, or raise datatypes to powers. This functionality is provided by the F# static type checker at compile time, **but units are erased from compiled code**. Consequently, it is not possible to determine value's unit at runtime. –  paul May 8 '13 at 13:45
add comment

Think about what your code is doing, and what it will allow. Having a simple enumeration with all the possible units in it allows me to do something like convert Volts to meters. That's obviously not valid to a human, but the software will gladly try.

I did something vaguely similar to this once, and my implementation had abstract base classes (length, weight, etc.) that all implemented IUnitOfMeasure. Each abstract bases class defined a default type (class Length had a default implementation of class Meter) that it would use for all conversion work. Hence IUnitOfMeasure implemented two different methods, ToDefault(decimal) and FromDefault(decimal).

The actual number I wanted to wrap was a generic type accepting IUnitOfMeasure as its generic argument. Saying something like Measurement<Meter>(2.0) gives you automatic type safety. Implementing the proper implicit conversions and math methods on these classes allows you to do things like Measurement<Meter>(2.0) * Measurement<Inch>(12) and return a result in the default type (Meter). I never worked out derived units like Newtons; I simply left them as Kilogram * Meter / Second / Second.

share|improve this answer
    
I like the approach you're suggesting with the use of generic types. –  GlenH7 May 8 '13 at 13:43
add comment

My prototype code: http://ideone.com/x7hz7i

My design points:

  1. Choice of UoM (Unit of Measure) as Property get/set
    Length len = new Length();
    len.Meters = 2.0;
    Console.WriteLine(len.Feet);
    
  2. Named constructor for choice of UoM
    Length len = Length.FromMeters(2.0);
    
  3. ToString support for UoM
    Console.WriteLine(len.ToString("ft"));
    Console.WriteLine(len.ToString("F15"));
    Console.WriteLine(len.ToString("ftF15"));
    
  4. Roundtrip conversion (negligible rounding loss permitted by double precision)
    Length lenRT = Length.FromMeters(Length.FromFeet(Length.FromMeters(len.Meters).Feet).Meters);
    
  5. Operator overloading (but lacking dimensional type check)
    // Quite messy, buggy, unsafe, and might not be possible without using F# or C++ MPL.
    // It goes on to say that Dimensional Analysis is not an optional feature for UoM -
    // whether you use it directly or not. It is required.
    
share|improve this answer
add comment

This is the raison d'etre of the Unix units command, which does it all using a data-file-driven approach for specifying the relationships.

share|improve this answer
    
Thank you for mentioning units. The main reason units won't work for my broader solution is the free form strings. Granted, it provides error messages in return, but this approach is targeting developers who will integrate this code with our application. Free form strings presents too much opportunity for error. –  GlenH7 May 9 '13 at 2:10
1  
You should take a look at units's data file. The way it defines relationships between quantities is very clean, and might be useful to your problem. –  Ross Patterson May 9 '13 at 10:36
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.