11,640,211 members (68,103 online)

# Units of Measure Library for .NET

, 20 Jun 2012 CPOL 19.9K 1.4K 32
 Rate this:
Please Sign up or sign in to vote.
This article introduces a library for handling units of measure.

## Introduction

This library provides several classes to work with units of measure, and this as near as possible to the SI standard. It gives the ability to load unit definitions from XML, to parse units, transform them and do calculations with them. Derived units like "km/h" are supported and can be constructed dynamically by dividing the unit "km" through "h". Beside shifted units like "Fahrenheit" and "Celsius", even custom units can be added. The attached demo project demonstrates, how to import self-updating currency units from the Internet. Thus, you can convert "l/EUR" into "cm^3/USD" by using up-to-date currency rates.

Since the library itself is fully XML documented, I will confine myself to the essentials.

This library is part of the Units of Measure Validator for C#.

## Concept of Units

There are three different types of units: Base units, scaled/shifted units and derived units.
Each unit has an optional name and an abbreviation. The abbreviation is required for units that are registered to an unit parser, and for base units, since the abbreviation defines the identity of a base unit.

Base units cannot be shortened or constructed from other units. An example is "Meter" or "Second". Two base units are equal, if their abbreviations are equal.

Scaled/Shifted units consists of a factor, an offset and an underlaying unit. An example is "Kilometer", which is 1000 "Meter". The underlaying unit can be any unit type, so you can define the unit "hour" as 60 minutes, and a minute as 60 seconds. Two scaled/shifted units are equal, if they have the same coherent unit and the same factor and offset in relation to this coherent unit. So "30 min" is equal to "half-an-hour", considering "min" is registered as "60 s" and "half-an-hour" as "0.5 hour", "30 min" or "1800 s" - all three options are possible. The offset is needed for unproportional units like degrees Fahrenheit or degrees Celsius. The coherent unit of Fahrenheit and Celsius is Kelvin.

Derived units are composed units with multiple parts, whereas each part consists of a unit of any type and an exponent. An example is "km/h", which is the same as "km^1 * h^-1" or "liter", which is the same as "dm^3". A derived unit is coherent, if all parts are coherent except for scaled/shifted unit parts: If a scaled/shifted unit is unproportional (i.e. an offset different from 0), its coherent unit is (but only for derived units) the most underlaying unit with the same offset. Thus, the coherent unit of cm/µ°C (centimeter per microcelsius) is m/°C, and not m/°K. This is, because a transformation from quantity a in m/°C to b in m/°K is not possible (see the chapter "Shifted Units in Connection with Derived Units").

A special case is the base unit Dimensionless unit, whose abbreviation is "1". A dimensionless unit is a neutral element, so any unit multiplied or divided by this special unit is the unit itself. The dimensionless unit divided by an other unit, is the reciprocal of the other unit (e.g. 1/m).

All units inherits from the `Unit` class, the dimensionless unit is represented by the static and read only `Unit.Dimensionless `property.

Units can be exponentiated, multiplied and divided using `Unit.Pow(int)`, `*` and `/`.

### Transformations

A `QuantityTransformation` describes a transformation for a value in one unit to another. Two transformations can be chained, if the target unit of the first transformation is equal to the source unit of second. Such a chained transformation describes a transformation from the very first unit to the very last.

With `Reverse`, each transformation can be reversed, so that the source unit and the target unit are exchanged. A transformation from meter to kilometer returns a transformation from kilometer to meter.

The `LinearQuantityTransformation` class is a `QuantityTransformation` and describes linear transformations like "`Transform(value) = Factor * value + Offset`".

Two transformations are equal, if their target units are equal and the mathematical transformation is the same. Two non-base units are equal, if their transformations to their coherent units are equal.

### What are Coherent Units?

A coherent unit does not need any factors (or offsets) to express relationships to other coherent units. All base units are coherent.

For example, the definition of newton:

,

all these units are coherent, thus, newton is coherent too.

It would be possible to define 1 newton' as

,

but since gram is no coherent unit, this definition is not coherent, as well as definitions containing any factors like

.

Nevertheless, it is possible to transform 1 newton' to the coherent newton by multiplying with 1000. This way, the unit of measure library can determine the equality between N' and N''.

## Parsing Units

Several formats are supported through different unit parsers, each of them implements `IUnitParser`. If parsing fails, either `null `is returned or a format exception will be thrown, depending on the `throwFormatException` parameter.

### Registered Unit Parser

This parser implements `IUnitParser` and `IUnitRegistry` and uses a string-unit dictionary to find the corresponding unit for a given string. "1" as dimensionless unit is registered by default.

### Prefixed Unit Parser

This parser is capable of parsing strings like "km" or "THz" by going through all available prefixes in the `Prefix` class and delegating the parsing of the underlaying unit ("m" for "km" or "Hz" for "THz") to an other parser (e.g. the Registered Unit Parser).

### Scaled Shifted Unit Parser

The Scaled Shifted Unit Parser parses `string`s consisting of a factor and a unit abbreviation (e.g. "60 s", the definition for 1 minute). The unit abbreviation is resolved through an injected `IUnitParser`. It is recommended to use a Composed Unit Parser, containing both a Registered Unit Parser and a Prefixed Unit Parser.

### Derived Unit Parser

With this parser, unit expressions like "m^2 * s^-1 / kg^2 * K" can be parsed.

The whitespaces are optional, and if the exponent is equal to 1, the exponent is optional too. Every exponent right from "/" is inverted. Although it is mathematically not correct, it looks much clearer without the braces (unless you convince me of the contrary in your comments).

The parsing of abbreviations is delegated to an injected `IUnitParser` as for the Scaled Shifted Unit Parser. It is recommended too, to use a Composed Unit Parser, containing both a Registered Unit Parser and a Prefixed Unit Parser.

### Composed Unit Parser

The composed unit parser aggregates multiple `IUnitParser` instances and returns the first successful result. This way, all the mentioned parsers can be combined into a single one.

The method `GetUnitRegistry` returns the first instance implementing the `IUnitRegistry` interface (or `null` if no such instance exists), so new units can be registered quickly into a composed unit parser.

The `static `method `NewDefaultUnitParser` returns a fully usable unit parser containing all parsers mentioned above.

### CachedUnitParser

This parser delegates the parsing to an injected unit parser and caches the result (the cache will not be emptied automatically).

## Loading Unit Definitions from XML

The class `XmlUnitLibrary` parses an XML document and loads the units into an `IUnitRegistry`. The XML document has to be compliant to a XSD schema which can be found in the source of the `HDUnitsOfMeasure `project.

Generally, the XML document looks like:

```<?xml version="1.0" encoding="utf-8" ?>
<UnitLibrary xmlns="http://www.hediet.de/xsd/unitlibrary/1.0">
<BaseUnit Name="Kelvin" Abbr="K"/>
<ScaledShiftedUnit Name="Celsius" Abbr="°C"
Factor="1" Offset="273.15" UnderlayingUnit="K" />
<ScaledShiftedUnit Name="Celsius" Abbr="°F"
Factor="0.55555555555555555555" Offset="255.37222222222222222222"
UnderlayingUnit="K" />

<DerivedUnit Name="Meters per Second" Abbr="mps">
<UnitPart  Unit="m" Exponent="1" />
<!-- the definition for m and s is left out in this snippet -->
<UnitPart  Unit="s" Exponent="-1" />
</DerivedUnit>
</UnitLibrary>```

## Threadsafety

In principle, using this library is thread safe, since each `Unit` is immutable or the access to members is synchronized. Parsing units is thread safe too as well as using the global unit parser.

## Demo

The attached C# demo project demonstrates, how to use the default units and import self-updating currency units from the Internet. You can enter a value, a source unit and a target unit. If a conversion is possible, the result will be displayed.

## Shifted Units in Connection with Derived Units

Generally, the transformation from one unit (x) to the underlaying (f) can be expressed by a function f(x).

Scaled/shifted units are described through

.

A unit is unproportional, if its offset is different from 0. This becomes a problem as soon multiple units are combined.

Assuming g(z):

.

If a value v is given in the unit x/z (1v = 1x/z), its transformation to v' in the unit f/g (1v' = 1f/g) can be described as:

.

Only if x and z are given or offset is 0, v' can be calculated.

Assuming offset is zero (if the unit x is proportional):

.

So if unproportional units are used in connection with derived units, they cannot be converted to their coherent equivalent. Nevertheless, µ°C (microcelsius) can be converted to °C, thus °C is the coherent unit for µ°C, if it is part of a derived unit (annotation: standalone, a transformation from µ°C to K is still possible).

For this purpose, the interface `ICouldBeUnproportional` has been introduced. Units which implement this interface, provides methods to get a proportional transformation to a "coherent" unit.

In principle, it is even not allowed to add two units with an offset:
1 °C + 1 °C = 274 K + 274 K = 548 K = 275 °C.

To solve this problem, you can use C° that describes a difference celsius:
1 °C + 1 C° = 274 K + 1 K = 275 K = 2 °C.
C° is an alias for Kelvin, thus a coherent unit too.

## History

• 1.1 Some updates
• 1.0 Initial release

## License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

## About the Author

 Student Germany
Presently I am a student of computer science at the Karlsruhe Institute of Technology in Germany.

## Comments and Discussions

 First Prev Next
 The library allows to add shifted unit values to base units MRusaev20-Oct-14 9:18 MRusaev 20-Oct-14 9:18
 very nice BillW3312-Aug-14 9:30 BillW33 12-Aug-14 9:30
 My vote of 5 Swab.Jat3-Feb-14 19:45 Swab.Jat 3-Feb-14 19:45
 factor/offset determination tubias27-Mar-13 22:04 tubias 27-Mar-13 22:04
 Similarity with Martin Fowler's measurement model heltonbiker23-Feb-13 6:22 heltonbiker 23-Feb-13 6:22
 could not parse "мкм". . .why? SergeyAB24-Aug-12 0:29 SergeyAB 24-Aug-12 0:29
 Parsers flamen_x30-Jun-12 11:28 flamen_x 30-Jun-12 11:28
 Re: Parsers Henning Dieterichs30-Jun-12 12:59 Henning Dieterichs 30-Jun-12 12:59
 Re: Parsers flamen_x2-Jul-12 2:27 flamen_x 2-Jul-12 2:27
 Celsisus & Kelvin Special Case? cjb11020-Jun-12 21:37 cjb110 20-Jun-12 21:37
 Re: Celsisus & Kelvin Special Case? Henning Dieterichs20-Jun-12 22:18 Henning Dieterichs 20-Jun-12 22:18
 I've to admit, it's a difficult topic. Think about the following paradoxon: 0 °C = 273 K. 0 °C - 0 °C = 273K - 273K = 0K = -273 °C. (?) The problem is, that differences of °C is not the same as °C. Typically, 1 such a difference °C is 1 Kelvin and not 274 Kelvin, so 0 °C - 0°C = 0°C. In this case, my library simply does not support to convert from difference °C to Kelvin. Is 1m / 1°C the same as 1m / 273K or the same as 1m / 1K? What happens, if you use the reciprocal value and multiplies with 1m? Is then still 1°C = 1K? Or 1°C = 274K? To avoid this problem, conversions from m/°C to m/K are simply not supported P.S.: I think it is only K, not degK (or °K ). If you find spelling- or grammer-mistakes, please let me know, so that I can correct them (at least for me) - english is not my first language...
 Re: Celsisus & Kelvin Special Case? cjb11020-Jun-12 23:52 cjb110 20-Jun-12 23:52
 Re: Celsisus & Kelvin Special Case? Henning Dieterichs21-Jun-12 0:21 Henning Dieterichs 21-Jun-12 0:21
 Re: Celsisus & Kelvin Special Case? cjb11021-Jun-12 3:46 cjb110 21-Jun-12 3:46
 Caution: doubles! Andreas Gieriet20-Jun-12 16:31 Andreas Gieriet 20-Jun-12 16:31
 Re: Caution: doubles! Henning Dieterichs20-Jun-12 19:43 Henning Dieterichs 20-Jun-12 19:43
 Re: Caution: doubles! motorboy7931-Jul-12 9:48 motorboy79 31-Jul-12 9:48
 Re: Caution: doubles! Henning Dieterichs2-Aug-12 5:33 Henning Dieterichs 2-Aug-12 5:33
 Last Visit: 31-Dec-99 18:00     Last Update: 31-Jul-15 21:58 Refresh 1

General    News    Suggestion    Question    Bug    Answer    Joke    Rant    Admin

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150731.1 | Last Updated 20 Jun 2012
Article Copyright 2012 by Henning Dieterichs
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid