Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Measurement Unit Conversion Library

3.92/5 (8 votes)
23 Jan 2008CPOL8 min read 1   4.2K  
This project allows you to store values along with unit information and conversion between different measurement systems. Also provides a hierarchy to create new converters easily.

Introduction

I usually build localizable web sites, sometimes I need to store values from different sources. Some metric, others not. A solution can be storing all values as metric, then convert to show them, easy but conversions among most different measurement systems aren’t exact so converted numbers look pretty ugly. This project allows you to store values along with unit information and conversion between different measurement systems. It also provides a hierarchy to create new converters easily.

All conversion constants used are not warranted to be the best, so be careful using results. I will show how to use this library with your own constants.

A Measurement Unit

I needed both to convert a value and knowing which unit a value represents. Then I needed this value to be stored easily on a database without using serialization or SQL User defined types which require assembly registration, not available in some web hostings. The easiest solution I could think of: use 32bit integer for unit description and 32bit for a floating point value, more than enough for all existing measurement units, and I think enough for most actual life values. Values would be stored in databases as 64 bit integers.

Int64_Layout.png

Units are represented by their Name, Symbol and Plural form for their name. Also they have a unique identifier within a table, described later. As they are supposed to be stored as integers, they have a property to convert them to and from Int64. Units are bound to tables, this way they only store information about their code and value, no strings no conversions, everything else they get from their tables.

A Unit Table

It is a table holding a set of measurement units and their conversions. Current implementations include XML initialization and resource localization.

Unit.PNG IUnitTable.png

Table instances can be used directly in order to get unit name, symbol, plural forms or converters, converters are explained later.

Hello World

Let’s suppose we have a table with the units: Meters, Centimeters, Feet and Inches, and all their conversions: Meters to Centimeters, Meters to Feet, Feet to Inches, and so on.

C#
UnitTable table = UnitTable.LengthTable;

Unit meters = new Unit(MetersCode, 100, table); 
Unit inches = new Unit(InchesCode, 120, table);

Console.WriteLine(meters.Convert(CentimetersCode));
Console.WriteLine(meters.Convert(FeetCode));
Console.WriteLine(meters.Convert(InchesCode));

Console.WriteLine(inches.Convert(MetersCode));
Console.WriteLine(inches.Convert(CentimetersCode));
Console.WriteLine(inches.Convert(FeetCode)); 

In this code excerpt, we take a predefined table named LengthTable, which contains some length measurement units and all cross conversions possible. Then we create 2 units, first represents 100 meters (m) the other 120 inches (in). Then we print 100 meters converted to centimeters, feet and inches, and later 120 inches converted to meters, centimeters and feet. You actually need to remember the codes you assigned to units when you first created the table, you could use const or enum to do this task easily. It is up to you.

The output from running previous sample was:

10000cm
328.0839ft
3937in
3.048006m
304.8006cm
10ft 

XmlTables

This is a clean and simple way to define units and conversions. Cross conversions can be a nightmare, I’m working on a Windows application to fill these files, but as I’m really bad building these kinds of applications, you should learn to write by yourself.

XML
<UnitTable>
    <Units>
        <Unit code="1" name="Meter" symbol="m" plural="Meters"/>
        <Unit code="2" name="Kilometer" symbol="km" plural="Kilometers"/>
        <Unit code="3" name="Centimeter" symbol="cm" plural="Centimeters"/>
        ...
        <Unit code="7" name="Foot" symbol="ft" plural="Feet"/>
        <Unit code="8" name="Inch" symbol="in" plural="Inches"/>
    </Units> 
    <Conversions>
        <Linear srcCode="1" destCode="2" factor="1e-3"/>
        <Linear srcCode="1" destCode="3" factor="1e2"/>
        ...
        <Linear srcCode="1" destCode="7" factor="3.280839"/>
        <Linear srcCode="1" destCode="8" factor="39.37"/>
        ...
        <Linear srcCode="7" destCode="8" factor="12" />
    </Conversions>
</UnitTable> 

Looks simple? It is. Let’s explain it anyway. Unit elements define units: code, name, international symbol and plural form. Linear element define a simple y = ax + b conversion, x will be source unit specified on srcCode attribute, y will be destination unit specified on destCode attribute, “a” will be “factor” and “deltha” which is not used on any of these conversions will be “b”. Parameter “deltha” is no very common as most measurement units have a very distinguished zero, some others don’t, take for instance temperature. In case conversions you need are not linear, you might use a Custom element (which is described later) instead. Both “factor” and “deltha” are double so you can use really precise constants.

Now we have a table file, let’s use it:

C#
UnitTable table = new XmlUnitTable("LengthUnits.xml");
Unit meters = new Unit(MetersCode, 100, table);
Unit inches = new Unit(InchesCode, 120, table);

If we use this excerpt instead of the one we use before, we will get the same result. XmlUnitTable constructor overloads support XmlDocument and Stream also.

A Decibel converter was also implemented, but as I’m not really sure what it can be used on, I won’t risk myself to ridicule and won’t use any sample. The idea is y = 10 log (X / Reference), Inverse is implemented but internal, so you might only use the Logarithmic implementation.

Decibel’s usage on an XML table file:

XML
...
<Conversions>
    <Decibel srcCode="1" destCode="2" reference="0.1"/>
    ...

Localized Tables

The main task for me, it would be useless for a web site if it isn’t localizable. This is a XmlUnitTable heir which also takes a ResourceManager and uses the values in name and plural as keys to get the actual one from the manager.

C#
public class LocalizedXmlUnitTable : XmlUnitTable
{
    ...
    public override string GetUnitName(int unitCode)
    {
        return man_Resources.GetString(base.GetUnitName(unitCode));
    }
} 

Now let’s take a Resource file named Units, which should have a line for each name and plural in the table file and create a localized table.

C#
LocalizedXmlUnitTable table = new LocalizedXmlUnitTable("LengthUnits.xml", 
    Resources.Units.ResourceManager); 

Now unit names and plurals are culture dependant, we could get “meters” in English, “metros” in Spanish and so on, we just need to write a resources file for each language. Unit symbols are not localizable because they are supposed to be international. I’m not completely but pretty sure they write “m” for meter in China, Russia, Israel, Greece or any other country not using the Latin alphabet. A Default resource manager is distributed along with the library, just in case you don’t want to write your own.

Converters

These are the real hard working guys from this library. Converters are formulas that convert from one unit to another one. You should ask a table for the correct formula; once you have it you can convert as many values as you want. You can also ask the Unit to convert itself and it will do the table asking thing for you. Some converters might be invertible, maybe all of them, but as I was writing this program, I didn’t have time to make a whole mathematical demonstration about if there might or not be a non-invertible formula to convert units, so converters have a property to ask them whether they can be or not inverted and a property to get the inverse. This way, if you define a converter from miles to meters, you don’t need to define one for meters to miles. The table will use the first one’s inverse to do the job. Anyway you can define both, explicit converters have priority over implicit ones.

IConverter.png

The current implementation includes a linear converter which can be inverted if factor isn’t zero, although it doesn’t make any sense because it would represent a constant conversion. It means factor equal zero is forbidden, don’t worry exceptions will make sure you don’t make the mistake.

Implementing Custom Converters

These classes can be used on the XmlTable and its heirs. They should have a public parameterless constructor and should be able to initialize themselves from a XmlElement.

XML
<UnitTable>
... 
    <Convertions>
        <Custom srcCode="3" destCode="1" typeName="HypotheticalCustomConverter"
            p1="v1" p2="v2" ... pn="vn"/>
        ...
    </Convertions>
</UnitTable> 

This table defines a custom converter of type HypotheticalCustomConverter. You should specify its name and as much information as you like in XML attributes. Using the method XmlInitialize, the converter should be able to take information from attributes and initialize itself.

CustomConverter.png

In the following example, we create custom converter, I used an hypothetical situation because I can’t think right now about any actual custom converter.

C#
public class HypotheticalCustomConverter : CustomConverter 
{
    object f1, f2, f3;
    public override void XmlInitialize(System.Xml.XmlElement element)
    {
        f1 = element.Attributes["p1"].Value;
        f2 = element.Attributes["p2"].Value;
        f3 = element.Attributes["p3"].Value;
    }
    public override bool AllowInverse { get { return true; } }
    public override IConversion Inverse
    { 
        get { return new HypotheticalCustomConverter(); } 
    }
    public override float Convert(float source) 
    {
        return source * source;
    }
} 

This converter might have no sense, is meant only to show how to implement some other than linear converters.

Let’s take for example the following implementation for a Fahrenheit to Celsius converter, it doesn’t need custom initialization and is a linear formula, but anyway.

C#
public class FahrenheitToCelsius : CustomConverter
{ 
    ...
    public override float Convert(float source)
    {
        return (float)(1.8 * source + 32);
    }
} 

If we build a analogous Celsius to Fahrenheit converter, we could define a property Inverse on FahrenheitToCelsius like this:

C#
public override IConverter Inverse
{
    get { return new CelsiusToFahrenheit(); }
} 

Finally a table file to use these 2 converters will look like this:

XML
<UnitTable>
    <Units> 
        <Unit code="1" name="Celsius" symbol="ºC" plural="PCelsius"/>
        <Unit code="3" name="Fahrenheit" symbol="ºF" plural="PFahrenheit"/>
    </Units>
    <Conversions>
        <Custom srcCode="3" destCode="1" typeName="Tests.FahrenheitToCelsius"/>
        <Custom srcCode="1" destCode="3" typeName="Tests.CelsiusToFahrenheit"/>
    </Conversions>
</UnitTable> 

I used Celsius and PCelsius instead of Degree Celsius and Degrees Fahrenheit to be able to use them as resource keys which cannot contain spaces, it means this file is intended to be used by LocalizedXmlUnitTable instances.

Currency Conversion

By definition they are linear conversions, except they are always changing. I built some classes to be able to download some rates from the internet or from a local XML file. You can always create some program creating an updated table file using linear conversions. This is just another way. There is a problem about using this library on currencies. Values are stored as float, which lose ciphers on rounding, so, NO serious financial applications should rely on my currency conversions. Currency conversion should be done using decimals. Maybe for future releases, I’ll think of a way to do both float and decimal, right now it is only float.

C#
CurrencyExchangeTable currTable = new CurrencyExchangeTable(
    new WebExchangeRatesProvider());

Unit eur = currTable.CreateUnit(200, "EUR");
Unit usd = currTable.CreateUnit(150, "USD");
Unit yen = currTable.CreateUnit(1000, "JPY");

Console.WriteLine(yen.Convert(currTable.CurrencyCode("USD")));
Console.WriteLine(yen.Convert(currTable.CurrencyCode("CAD")));
Console.WriteLine(yen.Convert(currTable.CurrencyCode("EUR")));
Console.WriteLine(yen.Convert(currTable.CurrencyCode("GBP"))); 

For currencies, it is easier to work with string constants, and as codes are auto assigned, there is no choice. Currencies tables should support creating units and getting unit codes from string constants. This table is a proxy for a rate provider, which actually gets rates from the web or some file, or any other source.

CurrencyExchangeTable.png

Rate Provider

Based on interface ICurrencyExchangeRatesProvider provides a way to get exchange rates from anywhere. This library contains a cached version and an online one.

ICurrencyExchangeProvider.png

Web Rates Provider

My default implementation for an online rates provider. It connects to a web site and applies an XSLT transformation to some XML source data to convert it into a DataSet compliant XML, then reads the DataSet. An XSLT for a default web site is provided.

The DataSet layout must be:

XML
<Rates>
    <Rate>
        <Currency>United States Dollar</Currency>
        <Exchange>1.4482</Exchange>
        <Symbol>USD</Symbol>
    </Rate>
    <Rate>
        <Currency>Euro</Currency>
        <Exchange>1</Exchange>
        <Symbol>EUR</Symbol>
    </Rate>
</Rates> 

To use this class on other than my default provider, you should create an XSLT file and use the following code:

C#
CurrencyExchangeTable currTable = new CurrencyExchangeTable(
    new WebExchangeRatesProvider("http://somesite.com/rate.xml", XSLStream)); 

You could also create you own provider by implementing interface ICurrencyExchangeRatesProvider.

To Do

I’m planning to gather as many measurement conversions as I can get and include them all as default tables, get some other currency exchange providers and provide XSLTs for all of them.

I need a way to store decimals and unit information on a database without using Serialization or User Defined Types. Once this is accomplished, it will be possible to change from float to double.

References

  1. Metric Conversions.org
  2. Waheed Khan, Currency Converter Server with C#

License

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