Product description

Small, simple and free solution to cope with units of measure in C# applications. It provides you with:
  • ability to built your own system of units of: Length, Time, Mass, Temperature, Electric Current, Amount of Substance, Luminosity and Money.
  • unit arithmetic operators (*-product, /-quotient, *-scale, ±-shift) to create new units,
  • quantity math (+, -, *, /, ++, --, Sqrt, Pow) and comparison operators ( ==, !=, <, <=, >, >=) to perform calculations on measured quantities,
  • quantity unit conversions,
  • generic types to best suit your precision requirements: Measure<T>, UnitOfMeasure<T>, UnitSystem<T>, Quantity<T>, Level<T>, T: float, double, decimal.
There exist an alternative solution with units designed as C# types.

Unit system

You can develop your own system of units, be it SI- or CGS-based or anything else that best suits your needs. E.g.:

// Length units
UnitOfMeasure<double> Meter = Unit(Magnitude.Length, "m", "meter"); // base unit
UnitOfMeasure<double> Centimeter = Unit(100.0 * Meter, "cm", "centimeter");
UnitOfMeasure<double> Kilometer = Unit(Meter / 1000.0, "km", "kilometer");
UnitOfMeasure<double> Inch = Unit(Centimeter / 2.54, "in", "inch");
UnitOfMeasure<double> Foot = Unit(Inch / 12.0, "ft", "feet");
UnitOfMeasure<double> Yard = Unit(Foot / 3.0, "yd", "yard");
UnitOfMeasure<double> Mile = Unit(Yard / 1760.0, "mil");
// Time units
UnitOfMeasure<double> Second = Unit(Magnitude.Time, "s", "second"); // base unit
...
// Mass units
UnitOfMeasure<double> Kilogram = Unit(Magnitude.Mass, "kg", "kilogram"); // base unit
...
// Temperature units
UnitOfMeasure<double> Kelvin = Unit(Magnitude.Temperature, "K", "deg.K"); // base unit
UnitOfMeasure<double> Celsius = Unit(Kelvin - 273.15, "°C", "deg.C");
UnitOfMeasure<double> Rankine = Unit((9.0 / 5.0) * Kelvin, "°R", "deg.R");
UnitOfMeasure<double> Fahrenheit = Unit((9.0 / 5.0) * Celsius + 32.0, "°F", "deg.F");
// Derived units
UnitOfMeasure<double> MeterPerSec = Unit(Meter / Second, "m/s"); // velocity
UnitOfMeasure<double> MeterPerSec2 = Unit(MeterPerSec / Second, "m/s2"); // acceleration
UnitOfMeasure<double> SquareMeter = Unit(Meter * Meter, "m2"); // area
UnitOfMeasure<double> CubicMeter = Unit(SquareMeter * Meter, "m3"); // volume
UnitOfMeasure<double> Newton = Unit(Kilogram * MeterPerSec2, "N"); // force
...

Note that units above are developed in a bottom-up fashion: base units first and then derived units built on top of it. This is accomplished via unit operators: product (*), quotient (/), scaling (* multiplying by a number), shifting (± adding/subtracting number to/from unit). Yet, it is not required to develop your system this way. You can specify units directly without specifying base units first:

Dimension force = new Dimension(1/*length*/, -2/*time*/, 1/*mass*/, 0, 0, 0, 0, 0);
UnitOfMeasure<double> Newton = Unit(new Measure<double>(force), "N" );

You can organize your set of units into UnitSystem<T> and query it to find required data:

UnitSystem<double> si = new SIUnitsDouble(); // see SIUnitsDouble.cs for details
...
UnitOfMeasure<double> meter = si["meter"]; // {m, meter [1*(L)]}
...
IEnumerable<UnitOfMeasure<double>> forceUnits = si.Units(force);
// [0]	{N [1*(LT-2M)]}		UnitOfMeasure<double>
// [1]	{dyn [100000*(LT-2M)]}	UnitOfMeasure<double>
...
IEnumerable<string> lengthSymbols = si.Symbols(Magnitude.Length);
// [0]	"m"	string
// [1]	"cm"	string
...

Quantities and Levels

Having defined units you can associate them with numerical (float, double, decimal) values into Quantity<T> or Level<T> variables. For example:

// Creating measured quantities using unit[value] indexer syntax:
Quantity<double> width = Meter[1.5]; // 1.5 m
Quantity<double> weight = Tonne[5.0]; // 5.0 tonnes
Quantity<double> speed = MilePerHour[65.0];	// 65.0 mph
...
Quantity<double> temperature = Celsius[100.0]; // increase of temperature of 100°C, equal to 100 K[elvins]
...
// Creating measured levels using unit.Level(value) method syntax:
Level<double> temperature = Celsius.Level(100.0); // temperature level of 100°C, equivalent to 373.15 K[elvins],

Level<T> represents position on a scale (e.g.Celsius scale), while Quantity<T> represents a change (increase, decrease or delta) of that level. Quantities are sufficient for most of the units - you do not need to bother with Levels. For temperatures, and any other shifted units i.e. units having fixed point of reference, it might be necessary to distinguish between Levels and their change (Quantities). See issue#1 for details.

Parsing text into Quantities / Levels

You can use UnitSystem.TryParse method to parse strings:
UnitSystem<double> dsi = new SIUnitsDouble(64, StringComparison.OrdinalIgnoreCase); // see SIUnitsDouble.cs for details
...
Quantity<double> result;
if (dsi.TryParse("123.45 mi/h", out result))
    Console.Writeline(result); // 123,45 mph (for "pl-PL" culture)
...
if (dsi.TryParse("3,125,000.0 m/s", new CultureInfo("en-US"), out result))
    Console.Writeline(result); // 3125000 m/s

Conversions

Quantities and Levels can be converted to any unit of the same dimension:

Level<double> celsius = Celsius.Level(100.0); // 100°C
// Conversions using unit[level] indexer syntax:
Level<double> fahrenheit = Fahrenheit[celsius]; // 212.0°F
Level<double> rankine = Rankine[fahrenheit]; // 671.67°R
Level<double> kelvin = Kelvin[rankine]; // 373.15 K
...
// Conversions using level[unit] indexer syntax:
Level<double> fahrenheit = celsius[Fahrenheit]; // 212.0°F
Level<double> rankine = fahrenheit[Rankine]; // 671.67°R
Level<double> kelvin = rankine[Kelvin]; // 373.15 K
...
Quantity<double> celsius = Celsius[100.0]; // 100°C
// Conversions using unit[quantity] indexer syntax:
Quantity<double> fahrenheit = Fahrenheit[celsius]; // 180.0°F
Quantity<double> rankine = Rankine[fahrenheit]; // 180.0°R
Quantity<double> kelvin = Kelvin[rankine]; // 100.0 K
...
Quantity<double> meters = Meter[100.0]; // 100.0 m
// Conversions using quantity[unit] indexer syntax:
Quantity<double> centimeters = meters[Centimeter]; // 10000.0 cm
Quantity<double> inches = centimeters[Inch]; // 3937.0078740157483 in
Quantity<double> feet = inches[Foot]; // 328.08398950131232 ft
Quantity<double> yards = feet[Yard]; // 109.36132983377077 yd
...
Quantity<double> length = dsi.Meter[15.0];
Level<double> temperature = dsi.Celsius.Level(0.0);
// Conversions using quotient syntax (quantity/unit or level/unit):
double lengthInFoot = length / dsi.Foot; // 49.212598425196852
double temperatureInKelvin = temperature / dsi.Kelvin; // 273.15

Converting to a unit of different dimension throws exception:

// The next statement throws exception:
yards = feet[Kelvin];

Level math & comparison operators

operator meaning
- subtracting levels: level - level = quantity
+ adding quantities to levels: level + quantity = level
* multiply level by number: level * number = level
/ divide level by number: level / number = level
/ divide level by unit: level / unit = number
++ level pre- & post-increment
-- level pre- & post-decrement
==, !=, <, <=, >, >= comparison operators

Quantity math & comparison operators

operator meaning
+ adding quantities
- subtracting quantities
* multiply quantity by quantity: quantity * quantity = quantity
* multiply quantity by number: quantity * number = quantity
/ divide quantity by quantity: quantity / quantity = quantity
/ divide quantity by number: quantity / number = quantity
/ divide quantity by unit: quantity / unit = number
++ quantity pre- & post-increment
-- quantity pre- & post-decrement
Sqrt(quantity) square root
Pow(quantity, num, den) raising quantity to a power
==, !=, <, <=, >, >= comparison operators

Having this you can perform calculations directly on quantities:

var meters = Meter[5.0]; // 5 meters
var centimeters = Centimeter[25.0]; // 25 centimeters
// sum of quantities:
var total = meters + centimeters; // 5.25 m
total = centimeters + meters; // 525 cm

var width = Meter[5.0]; // 5m
var height = Yard[6.0]; // 6 yd
var depth = Foot[4.0]; // 4 ft
// product of quantities:
var volume = width * height * depth;
Console.Writeline(CubicMeter[volume]);  // 33,4450944 m3

var distance = Mile[50.0]; // 50 mil
var duration = Minute[30.0]; // 30 minutes
// quotient of quantities:
var speed = distance / duration;
Console.Writeline(MilePerHour[speed]) // 100 mph
Console.Writeline(KilometerPerHour[speed]) // 160.9344 km/h

Level<double> initial = Celsius.Level(100.0); // initial level at 100°C
Quantity<double> delta = dsi.Fahrenheit[9.0]; // change (delta) of 9°F
// level-quantity arithmetic:
Level<double> final = initial + delta; // final level at 105°C
delta = final - initial; // 5°C

You can even perform quite complex computations without resorting to plain numbers:

// Trajectory of projectile
using QDouble = Man.UnitsOfMeasure.Quantity<double>;
...
QDouble g = si.MeterPerSec2[9.81]; // the gravitational acceleration
QDouble v = si.MeterPerSec[715.0]; // the velocity at which the projectile is launched (AK-47)
QDouble h = si.Meter[0.0]; // the initial height of the projectile
QDouble angle = si.Radian[si.Degree[45.0]]; // the angle at which the projectile is launched

// the time it takes for the projectile to finish its trajectory:
QDouble tmax = 
    si.Second[(v * Math.Sin(angle.Value) + QDouble.Sqrt(QDouble.Pow(v * Math.Sin(angle.Value), 2) + 2.0 * g * h)) / g];

QDouble ymax = h;
for (QDouble t = si.Second[0.0]; t < tmax; t++)
{
    QDouble y = h + v * Math.Sin(angle.Value) * t - g * t * t / 2.0;
    if (y > ymax) ymax = y;
}

// the total horizontal distance traveled by the projectile
QDouble xmax = si.Meter[v * Math.Cos(angle.Value) * tmax];

However, bear in mind that this is much more time-consuming than analogous calculation made on plain numbers. Any operation on quantities involves creation and/or conversion of units, so use the solution sparingly. It is not a good idea to use it in CPU-intensive computations, but it is more than sufficient to deal with units on input/output e.g. while waiting on user input in dialog box fields. Run Demo application to see quantities/plain calculation performance (sample) difference.

Last edited Sep 3, 2014 at 5:00 PM by manio, version 22