Saturday, 8 November 2014

The workings of TLCalc's unit converter

I'm sure what I present here is fairly obvious to most people. But then again, when I look through all the apps on Android and iOS, I don't see many apps offering the capability of TLCalc's unit converter. To keep this post short, we will only cover scalar units and its concept (non-scalar units and code will be left for another day).

Generic unit conversion


Its pretty simple to do conversion of units you are expecting such as metres to inches. You simply need to know the factor to multiply with and the inverse operation is as simple as dividing. But when you want to cover more complicated units such as energy, force and speed, you will find that doing things this way results in large permutations of units that you should cover. Take speed as an example, you can have multiple distance units over a common time unit such as m/s, in/s, cm/s, mm/s, ft/s, and etc. But you can also measure speed as a ratio over other time units such as hours, minutes, days and so on (I admit some of these are infrequently used but you never know). If you were to define the scaling constant between each unit like this, your conversion table would grow exponentially.

Flexible unit conversion


The solution is simple, convert each unit one by one. E.g. convert mi/hr to m/s
The conversion factor for mi to m is 1,609 and from hr to s is 3600 thus: mi/hr = 1,609/3600 m/s = 0.447 m/s (the precision used here is low but you get the point). Intuitively, this is how you would go about converting units anyway. 

To improve flexibility even more, converting should be done to a common unit first then to the desired unit. E.g. convert length to metres, time to seconds, mass to kilograms and so on. This will reduce the size of the conversion table even further and make updating your data much easier. The common unit chosen is not critical, as long as it is consistent but choosing SI units has a bonus of reducing errors as most conversion will be to or from SI units thus eliminating errors from multiplication of two possibly imprecise factors to one multiplication. 

As such, the table of factor I use in my app is very small. The following is an extract from the full data set. The name contains the unit name as well as its abbreviation (basically all the way you might name the unit when typing) separated by semicolons. The "type" and "allow prefix" columns will be covered later while "linear" indicates whether the unit is a linear conversion (this post only covers scalar units so this will be covered in a later post). m/forward contains the factor to convert from that unit to common unit. c/backward is for converting from common unit to that unit. It is left blank here as "c/backward" = 1/"m/forward" so it can be calculated at runtime to reduce file size. The "m/backward" column exists only for non-scalar units which is not covered in this post. The data is saved as a CSV file to allow easy importing into the app. 
 
 
 

Dimensionality analysis


Without placing constraints, it would be possible to convert metres to gram using the table above although the result would be nonsense. So to impose constraints, we only allow conversions if the units are equivalent in quantity, e.g. length to length, speed to speed but not length to time. The check that is used is something called dimensional analysis (something that I learnt in physics in year 12!). The analysis is to figure out how to break units down into the most basic units and compare the basic units. E.g. speed is a ratio of length over time so any units involving a length divided by time is equivalent. There are in total 4 quantities that cannot be decomposed further which are length (L), time (t), mass (M) and temperature (T). The rest are composed of combinations of these quantities. In the data table above, the type column defines the unit's dimensions.
 
There is another category known as dimensionless quantities. These have no impact on the dimensions hence the name. Example of dimensionless units include radians and degrees. My implementation also includes bit, bytes, percentage and decibels as dimensionless as they can apply to any quantity (but I'm not quite sure if this is standard).
 
Note that in the system of SI units, there are in fact 7 fundamental quantities with current (amp), the amount of substance (mol) and luminous intensity (cd) being the remaining 3. The calculator at this stage does not include these units so they are ignored.

SI Prefix


The last section I will cover in this post are SI units. SI units are basically scalar units. E.g. kilo = 1000, milli = 0.001, etc. For programming, they were treated as such although parsing was a bit more strict than just allowing SI prefix to float anywhere. SI prefix can only be attached to the front of valid units and only one SI prefix is allowed per base unit so kilomilligram is not allowed but millimetres / kilogram is allowed. I was lax with the implementation of the unit converter so you can add SI prefix to non-SI units such as pounds although this is highly uncommon or never done. The data in the table above allows you to specify which units can have SI prefix although the calculator doesn't enforce this. The data is only used in the autocomplete functionality to make the results more relevant. E.g. TLCalc will never suggest klb (kilopound) although it will suggest kg (kilogram).
 
You can download the full data file here although the version linked will very likely be updated frequently. In the next post, I will post some code on how all this was implemented in TLCalc.