Dan's ConversionCalc Library

Isaac Newton, in his workshop, comparing a bag of flour with a teacup.

An easy-to-implement function library providing generic numeric conversions in a wide array of possible scenarios for engineering and practical applications.

 

Table of Contents

You can jump to any section of this page from the following list.

 

25 Years of .NET

Although .NET doesn't officially turn 25 until February 13, 2027, I'm starting the celebration a little early.

To commemorate 25 years since the public release of the .NET framework, I'm open sourcing this and several other of my long-lived libraries and applications. Most of these have only previously been used privately in our own internal company productivity during the early 21st century but I hope they might find a number of new uses to complete the next 25 years.

I have every intention of keeping these libraries and applications maintained, so if you happen to run into anything you would like to see added, changed, or repaired, just let me know in the Issues section and I'll get it done for you as time permits.

 

Sincerely,

Daniel Patterson, MCSD (danielanywhere)

 

Yet Another Value Converter

The main use of this conversion library has always been to allow the user to enter any kind of value in a unit of measurement that makes sense to them, then to successfully respond with real world results in a different unit of measurement. A couple of great examples of this kind of conversion are:

In fact, this library serves to be more of a universal engineering converter for practical everyday applications than a computing converter in the literal sense of the word, which I believe is where I deviated from the norm.

I absolutely hate re-inventing the wheel all the time, so as usual, when I began on this library many years ago, I had definitely already searched high and low for generic value converters. Out of the closest libraries I could find, most were completely hard-coded, but if not, they would probably utilize some strange look-up table, not be extensible in any way, be mainly concerned with the conversion of binary data from one datatype to another, or some other unique makeup that I either didn't want or need.

However, as I mentioned, this library has also been under active maintenance for several years - this code has existed in one form or another since 1997 to be exact, starting as VBA clear back in the days of Microsoft Office Automation, so there weren't too many generic, non-proprietary things along these lines that even existed in that time.

Let's be clear, though. Contrary to the modern IT way of thinking (mainly proliferated by people who don't ever directly develop technology), software doesn't ever age - it just either works perfectly or it doesn't. I maintain that this converter won't seem old or outdated to you in any way. You are likely to find only the most up-to-date methods and techniques, and somewhat efficient use of your definitions throughout each conversion. Even though I admit there's always room for improvement, you can be sure not to experience any of the clunkiness in this library that so many projects seem to carry as constant baggage. This package has already been around the block more than a few times and should be ready for anything you may need to throw at it.

Whatever the library might lack in forward momentum or future vision, it makes up for by far in consistency, accuracy, and outright extensibility. If you have a set of specialized conversions to implement in your any-format-accepted textbox and don't mind pulling out a couple of regular expressions once in a while to tend to inter-category complex equational traffic management, then this library is probably for you.

I've made sure to provide a few different kinds of examples, too, that might help you decide how you might want to use the functionality. Just start an Issue in the toolbar above if you need to see a few more examples or if you run into a problem where something isn't working as expected.

 

A Dictionary-Driven Converter

In most cases, you might never need to make any changes to the base code. This library allows you to extend or even entirely change the types of calculations that can be performed by maintaining your own JSON unit catalog. Although you can either add to or replace the calculation definitions at will, the built-in calculations are meant to be helpful in nearly every practical scenario. I'll likely be adding more all the time to work with our own projects.

 

See the JSON Catalog Format appendix at the bottom of this page for more information on editing the dictionary data or the file Data/DefaultData.json, in this project, for practical examples of how the conversion definitions are structured.

 

Installation

You can include this library in any .NET project using any supported programming language or target system. This library compiles as .NET Standard 2.0 and is available in NuGet as

Dan's ConversionCalc Library

 

Instructions For Installation

In Visual Studio Community or Visual Studio Professional editions:

 

In Visual Studio Code:

 

Usage Notes

For the full documentation of this library, you can access the API in HTML format at the associated GitHub user page library https://danielanywhere.github.io/ConversionCalc.

The main object in this library is instantiated from the Converter class. In general, create a new instance of Converter and call its various methods, as in the following example.

using System;
using ConversionCalc;

// Simplest possible use of ConversionCalc.Converter.
namespace MyProject
{
 public class Program
 {
  public static void Main(string[] args)
  {
   Converter converter = new Converter();
   double toValue = converter.Convert(60, "mph", "mi/sec");
   Console.WriteLine($"Answer: {toValue} mi/sec");
  }
 }
}


 

Example 1 - Teaspoons to Cups

This easy example shows you how to convert 25,479 teaspoons to cups, using the implicit domain lookup feature.

using System;
using ConversionCalc;

// Convert 25,479 teaspoons to cups, using implicit domain lookup.
namespace MyProject
{
 public class Program
 {
  public static void Main(string[] args)
  {
   Converter converter = new Converter();
   double toValue = converter.Convert(25479, "tsp", "cups");
   Console.WriteLine($"Answer: {toValue} cups");
  }
 }
}


 

Example 2 - Intercategorical Conversions

In this next admittedly preconceived example, while slightly more involved than the previous one, I show you how to consistently handle multiple conversions for different domains based upon the single user input of 1000 km/sec to mi/hr.

To make it interesting, instead of using the built-in conversions that could solve this query in one pass, the distance units are going to be separated from the time units, and different conversions will be performed in each domain. Notice that in the second conversion, we are flipping the order of the source and target units in the call to converter.Convert because the values in the second calculation are denominators of a division (ie miles/hr as opposed to miles*hr).

using System;
using ConversionCalc;

// Multiple conversions for different domains for
// user input of 1000 km/sec to mi/hr.
namespace MyProject
{
 public class Program
 {
  public static void Main(string[] args)
  {
   Converter converter = new Converter();
   ConversionDomainItem domain = null;
   string fromUnit = "";
   Match match = null;
   string toUnit = "";
   string userInput = "1000 km/sec to mi/hr";
   double value = 0d;

   Console.WriteLine($"Convert {userInput}.");
   match = Regex.Match(userInput,
    @"^\s*(?<value>-{0,1}[0-9]+(\.[0-9]+){0,1})\s+" +
    @"(?<fromUnit1>\w+)\s*(\s*/\s*(?<fromUnit2>\w+)){0,1}\s+to\s+" +
    @"(?<toUnit>(?<toUnit1>\w+)\s*(\s*/\s*(?<toUnit2>\w+)){0,1})");
   if (match.Success)
   {
    //	The first conversion is required.
    value = ToDouble(GetValue(match, "value"));
    fromUnit = GetValue(match, "fromUnit1");
    toUnit = GetValue(match, "toUnit1");
    if (fromUnit.Length > 0 && toUnit.Length > 0)
    {
     //	From and to were both provided.
     domain = converter.FindDomain(fromUnit, toUnit);
     if (domain != null)
     {
      //	A common domain was found.
      Console.WriteLine($"Using domain: {domain.DomainName}");
      value = converter.Convert(domain, value, fromUnit, toUnit);
      Console.WriteLine($"Intermediate. {toUnit} = {value}.");
      //	The second conversion is optional.
      domain = null;
      fromUnit = GetValue(match, "fromUnit2");
      toUnit = GetValue(match, "toUnit2");
      if ((fromUnit.Length > 0 || toUnit.Length > 0) &&
       (fromUnit.Length == 0 || toUnit.Length == 0))
      {
       //	Only one unit was supplied.
       if (fromUnit.Length > 0)
       {
        //	The from unit was supplied. Get the default to unit.
        domain = converter.FindDomain(fromUnit);
        if (domain != null &&
         domain.Conversions.Exists(x =>
          x.EntryType == ConversionDefinitionEntryType.Base))
        {
         toUnit = domain.Conversions.First(x =>
          x.EntryType == ConversionDefinitionEntryType.Base).Name;
        }
       }
       else
       {
        //	The to unit was supplied. Get the default from unit.
        domain = converter.FindDomain(toUnit);
        if (domain != null &&
         domain.Conversions.Exists(x =>
          x.EntryType == ConversionDefinitionEntryType.Base))
        {
         fromUnit = domain.Conversions.First(x =>
          x.EntryType == ConversionDefinitionEntryType.Base).Name;
        }
       }
      }
      else if (fromUnit.Length > 0)
      {
       //	Both units were supplied.
       domain = converter.FindDomain(fromUnit, toUnit);
      }
      if (domain != null && fromUnit.Length > 0 && toUnit.Length > 0)
      {
       Console.WriteLine($"Using domain: {domain.DomainName}");
       //	The unit references are flipped on the second element because
       //	we want a division of the original unit instead of
       //	a multiplication.
       //	ie miles/hr as opposed to miles*hr.
       value = converter.Convert(domain, value, toUnit, fromUnit);
      }
      Console.WriteLine($"Answer: {value} {GetValue(match, "toUnit")}");
     }
     else
     {
      Console.Write("Error: Common domain not found for ");
      Console.WriteLine($"{fromUnit} and {toUnit}.");
     }
    }
   }
  }
 }
}


 

Appendix - Built-In Measurement Categories

Several units of measure have been included by default to provide normal conversions for many of life's daily demands.

The individual unit measurements are partitioned into the following major measurement categories, or domains, to which they apply.

[Bracketed] values next to a unit name indicate other names by which that unit is equally recognized.

 

Angles

The angles category is sparse in this version with only a conversion path between radians and degrees.

 

Units

The following units are currently available in this domain.

 

Binary Data

This version of the binary data domain maintains the original processor-agnostic binary theory that the basic sizes of all units are universally permanent and static.

 

In this version of that system, the following relations always hold true:

Entity Expression
BIT 1 binary unit
BYTE 8 BITS
WORD 2 BYTES
DOUBLE WORD 2 WORDS
QUAD WORD 4 WORDS

 

Units

The following discrete units are available in this domain.

 

Circles

A circle is a fascinating geometric shape with several key parts that are all interconnected. The radius is the distance from the center of the circle to any point on its edge. The diameter is twice the length of the radius, stretching from one edge of the circle to the opposite edge, passing through the center. The circumference is the total distance around the circle, which can be calculated using the formula C=2𝜋r, where r is the radius. The area of the circle, representing the space enclosed within its boundary, is given by the formula A=𝜋r2. Each of these parts plays a crucial role in defining the properties and measurements of a circle.

 

Units

Here are the units in this version of the Circles category.

 

Count

The Count domain translates different general methods for counting objects. Certain counting terms that have no discrete basis, like set and fleet have been omitted.

 

Units

The following discrete units have been built-in to the count domain.

 

Density

The density of a material is the ratio of its mass to the volume it occupies.

Density is influenced by the mass of the atoms, their dimensions, and their physical arrangement.

Density is calculated by dividing the mass of the material by its volume, represented by the formula: D=m/v.

 

Units

The following default units of measure have been included in this domain.

 

Distance

Distance can be measured in various units, from nanometers (nm) for tiny objects to light years for astronomical distances. A millimeter is useful for small-scale measurements, while a light year, about 9.46 trillion kilometers, measures the vast distances light travels in a year. These units help us describe everything from the microscopic to the cosmic scale.

 

Units

The distance domain includes the following units of measure.

 

Power

Power measurements help us understand the rate at which energy is used or transferred.

 

Units

The following power units are defined in the built-in catalog.

 

Pressure

Pressure measurements are essential in various fields, from engineering to meteorology. These units help us quantify and compare pressures in different contexts, ensuring accurate and consistent measurements across various industries.

 

Units

Following are the predefined units available in the pressure category.

 

Temperature

Temperature measurements are crucial in various fields, using different scales like Fahrenheit (°F), Celsius (°C), and Kelvin (K). These scales help us accurately measure and communicate temperature in diverse applications, from weather forecasting to scientific research.

 

Units

Here are the default temperature units built-in on this version.

 

Time

Time can be measured in various units, each suited for different scales. For instance, a nanosecond (ns) is one-billionth of a second, often used in high-speed computing and electronics. These units allow us to measure and understand time from the incredibly brief to the vast expanses of history.

 

Units

These units have been provided by default in the time domain.

 

Volume

Volume can be measured using various units depending on the context. These units help us quantify and compare volumes in different settings, from everyday cooking to industrial applications.

 

Units

These units of measurement have been preset on the built-in data catalog.

 

Weight

Weight can be measured using various units depending on the context. These units help to accurately measure and compare weights in various applications, from cooking to industrial use."

 

Units

The following list contains units of weight-based measure included in the built-in conversion dictionary.

 

Appendix - JSON Catalog Format

As alluded to above, each unit-to-unit calculation within a domain is defined by a series of unidirectional unit definitions that occupy what is being referred to as a domain.

In all cases, a single conversion entry represents what it will take to convert the incoming value to the representative base unit, and in most cases, a conversion entry can be completed using one primitive mathematical operation.

The general schema for conversion catalog definitions follows. The main groupings in the pattern are the collection of domains, each domain's collection of conversions, and a conversion's procedural steps in the case that the conversion entry is of a procedural type.

 

 

In the above structure, note the following:

 

The following empty JSON template exemplifies the general shape of the catalog. Keep in mind that Remarks are optional, the default value for Operation is "Multiply" when omitted, and the Steps collection only appears when EntryType = "Procedure".

 

{
 "Remarks": [ "" ],
 "Domains":
 [
  {
   "DomainName": "",
   "Remarks": [ "" ],
   "Conversions":
   [
    {
     "EntryType": "",
     "Name": "",
     "Aliases": [ "" ],
     "Operation": "",
     "RefName": "",
     "Value": 1,
     "Steps":
     [
      {
       "RefName": "",
       "Operation": "",
       "Value": ""
      }
     ]
    }
   ]
  }
 ]
}


 

NOTE: Also see the file Data/DefaultData.json, in this repository, for examples of how the base conversions have been prepared.