TL;DR – I’m trying to design an 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.meterSIUnit.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.
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.
9
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.
2
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);
4
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 *)
2
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 theunit
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’sunit
. TheUOM
class provides overloaded operators for adding/subtracting anotherUOM
and for multiplying/dividing with a dimensionless value.If addition/subtraction is performed on two
UOM
with the sameunit
, 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 baseunit
.
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.
7
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.
1
I believe the answer lies in MarioVW’s Stack Overflow response to:
Practical Example Where Tuple can be used in .Net 4-0?
With tuples you could easily implement a two-dimensional dictionary (or n-dimensional for that matter). For example, you could use such a dictionary to implement a currency exchange mapping:
var forex = new Dictionary<Tuple<string, string>, decimal>();
forex.Add(Tuple.Create("USD", "EUR"), 0.74850m); // 1 USD = 0.74850 EUR
forex.Add(Tuple.Create("USD", "GBP"), 0.64128m);
forex.Add(Tuple.Create("EUR", "USD"), 1.33635m);
forex.Add(Tuple.Create("EUR", "GBP"), 0.85677m);
forex.Add(Tuple.Create("GBP", "USD"), 1.55938m);
forex.Add(Tuple.Create("GBP", "EUR"), 1.16717m);
forex.Add(Tuple.Create("USD", "USD"), 1.00000m);
forex.Add(Tuple.Create("EUR", "EUR"), 1.00000m);
forex.Add(Tuple.Create("GBP", "GBP"), 1.00000m);
decimal result;
result = 35.0m * forex[Tuple.Create("USD", "EUR")]; // USD 35.00 = EUR 26.20
result = 35.0m * forex[Tuple.Create("EUR", "GBP")]; // EUR 35.00 = GBP 29.99
result = 35.0m * forex[Tuple.Create("GBP", "USD")]; // GBP 35.00 = USD 54.58
I had a similar need for my application. Tuple
is also immutable which is also true of objects such as Weights and Measures… As the saying goes “a pint a pound the world around.”
My prototype code: http://ideone.com/x7hz7i
My design points:
- Choice of UoM (Unit of Measure) as Property get/set
Length len = new Length(); len.Meters = 2.0; Console.WriteLine(len.Feet);
- Named constructor for choice of UoM
Length len = Length.FromMeters(2.0);
- ToString support for UoM
Console.WriteLine(len.ToString("ft")); Console.WriteLine(len.ToString("F15")); Console.WriteLine(len.ToString("ftF15"));
- Roundtrip conversion (negligible rounding loss permitted by double precision)
Length lenRT = Length.FromMeters(Length.FromFeet(Length.FromMeters(len.Meters).Feet).Meters);
- 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.
There is a good article in a magazin unfourtunatly it is in German: http://www.dotnetpro.de/articles/onlinearticle1398.aspx
Base idea is to have a Unit class like Length with a BaseMeasurement. The class contains the conversion factor, operator overloads, ToString overloads, parser of strings and an implementation as an indexer. We’ve even implemented even the Architectual view but it’s not release as a library.
public class Length : MeasurementBase
{
protected static double[] LengthFactors = { 1, 100, 1000, 0.001, 100 / 2.54 };
protected static string[] LengthSymbols = { "m", "cm", "mm", "km", "in" };
...
public virtual double this[Units unit]
{
get { return BaseValue * LengthFactors[(int)unit]; }
set { BaseValue = value / LengthFactors[(int)unit]; }
}
...
public static ForceDividedByLength operator *(Length length, Pressure pressure1)
{
return new ForceDividedByLength(pressure1[Pressure.Units.kNm2] * length[Units.m], ForceDividedByLength.Units.kNm);
}
...
So you see the usage with the Pressure operator or just:
var l = new Length(5, Length.Units.m)
Area a = l * new Length("5 m");
a.ToString() // -> 25 m^2
double l2 = l[Length.Units.ft];
But as you said, I didn’t find the unicorn either 🙂
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.
2