Unit Conversion and Dimensional Analysis Library  2.1.0
A compile-time c++14 unit conversion library
UNITS

A compile-time, header-only, dimensional analysis library built on c++14 with no dependencies.

license
copyright
language
c++


msvc2013
msvc2015
gcc-4.9.3
gcc-5.4.0
clang-3.4

Latest Release - v2.2.0

Get it

Special Thanks

Thank you to the contributors who made this version possible!

New feautres in v2.2.0

constexpr auto distance = 5_m;
constexpr auto area = 2_m * 2_m; // area == 4_sq_m
constexpr auto volume(units::math::cpow<3>(2_m)); // volume == 8_cu_m
meter_t a = constants::pi * 1_m; // a == PI meters
meter_t b(constants::pi); // b == PI meters
class myClass
{
public:
myClass() : m_speed(make_unit<miles_per_hour_t>(100)) {}
private:
miles_per_hour_t m_speed;
};

of course, explicit initializations are still supported.

meter_t distance_m(10);
meter_t distance(10_m);
using namespace units::math;
auto hypotnuse = hypot(3_m, 4_m); // hypotnuse == 5_m
nanosecond_t a = std::chrono::nanoseconds(10); // a == 10_ns
std::chrono::nanoseconds b = hour_t(1); // b.count() == 3600000000000

New features in v2.1.3

auto area = 3.0_m * 4.0_m; // area == 12_sq_m
mile_t distance(26.2);
std::cout << distance; // printed: 26.2 mi
mile_t distance(26.2);
double result = unit_cast<double>(distance); // result == 26.2
UNIT_ADD(length, foot, feet, ft, unit<std::ratio<381, 1250>, meters>)

Notes

Tested on

Does this library work on your compiler? If so, let me know!

Contents

Documentation

The full documentation is available ***here***.

Description

The library consists of a single file (units.h), plus unit tests. To incorporate the library into your project, simply copy the header into a location in your include path, or add the included CMake project into your build. Using the CMake project, you can also build the unit tests and documentation if desired.

The library provides a set of types, containers, and traits to solve dimensional analysis problems, that is, problems involving dimensioned physical quantities. The conversions between units are defined as ratios at compile time, making the library incredibly fast. Additionally, specifying units as types, rather than variable suffixes (or not at all), provides complete type-safety within the compiler. This means that code that accidentally misuses units or which has errors in the dimensional analysis will fail at compile-time, not at run-time.

The unit test file unitTests/main.cpp contains example usage of every type, trait, and function contained in the library, and while not exactly user-friendly, can be a valuable resource.

Getting started guide

Add units.h to your project, along with the using directive for literals

#include <units.h>
using namespace units::literals;

Each "dimension" of unit is defined in its own namespace. See the namespaces section for a complete list. The rest of the guide assumes you've included the namespaces you plan to use:

using namespace units;
using namespace units::length;
using namespace units::time;
using namespace units::area;
using namespace units::velocity;

The easiest way to get started with the units library is to think of unit containers as double values. Unit containers are typically the units' non-plural name with the suffix _t (for type), e.g. meter_t. See the documentation for a complete list.

Units can (and should!) be used anywhere double values can be used:

double area = 15 * 5 + 10 * 10; // 175 m^2?
square_meter_t area = 15_m * 5_m + 10_m * 10_m; // 175 m^2

What makes unit types special is that unit conversions happen implicitly and automatically. Since unit conversions are evaluated at compile time, this means you can mix and match all the unit types you want with no runtime penalty.

foot_t len = 5_m; // simple implicit conversion
meters_per_second_t speed = 60_mi / 1_hr; // more complex implicit conversion
square_meter_t area = 15_m * 5_m + 1000_cm * 1000_cm; // previous example with mixed units

Note the return type has the correct dimensions of area, even though the source types were all units of length. units.h has powerful dimensional analysis capabilities. But what happens if we get the return type wrong?

meter_t area = 15_m * 5_m + 10_m * 10_m; // oops, m * m = m^2

E:/workspace/units/include/units.h(1405): error C2338: Units are not compatible.

Your compiler will produce an "incompatible units" error if your dimensional analysis is incorrect. If your resulting unit types are complex, you could use auto for simplicity:

auto result = 15_m * 5_m + 10_m * 10_m; // m^2
auto speed = 60_mi / 1_hr; // 60 mph

***NOTE: Think carefully about using auto for return types.*** When you explicitly declare the return type, the compiler can check the dimensional analysis for correctness, and produce errors at compile time if you make a mistake. When using auto, you are basically saying that whatever unit the right-hand side of the expression results to is correct (even if it's not). If you are only using auto because a complex unit type is not available in the library, try defining a new unit as a better alternative.

More complex mathematical operations (almost every <cmath> operation actually), including exponentials and square roots are possibe by using the units::math namespace .

using namespace units::math;
meter_t a = 3_m;
meter_t b = 4_m;
meter_t c = sqrt(pow<2>(a) + pow<2>(b)); // Pythagorean threorem.
std::cout << c << std::endl; // prints: "5 m"

Unit tags

Unit tags are the foundation of the unit library. Unit tags are types which are never instantiated in user code, but which provide the meta-information about different units, including how to convert between them, and how to determine their compatibility for conversion.

All unit tags are defined in namespaces under the units namespace, such as units::length or units::angle, to avoid name clashes between units of different physical quantities which share the same names (like pounds). SI base units are defined as "categories" in the unit namespace.

Units are defined in terms of

  1. A scale factor relative to a base unit type.
  2. A base unit
  3. [optionally] a scale factor of pi
  4. [optionally] a datum translation (such as the +/- 32 required to convert between fahrenheit and celsius)

All units have their origin in the Scientific International (SI) base unit system. A special exception is made for angle units, which are defined in SI as ( m * m^-1), and in this library they are treated as a basic unit type because of their important engineering applications.

Example: the definitions of some common length units are:

namespace length
{
using meters = units::unit<std::ratio<1>, units::category::length_unit>; // meters are (1) unit of length in the SI system.
using feet = units::unit<std::ratio<381, 1250>, meters>; // feet are 0.3048 meters.
}

Unit containers

Unit containers are the primary classes which will be instantiated in user code. They can be thought of as essentially equivalent to a double, except that they have unit type tags associated with them. They can be used wherever a double would be used to store a dimensioned quantity. Containers are derived from the unit_t class, and have the form [unitname]_t, e.g. meter_t or radian_t.

Unit containers are defined in terms of the units they represent, their underlying type, and an optional non-linear scale (think decibels or Richter scale). For example, meter_t would be defined:

or simply

since the underlying type and scale parameters default to double and linear_scale respectively.

Units of compatible types (e.g length units) can be implicitly converted/assigned to one another. Units (with the exception of dimensionless types) cannot be implicitly converted to/from built-in types, such as double.

Units are constructed from built-in types, and the toDouble() method (or operator()) can be used to retrieve a built-in type value. That said, the user should prefer to operate within the unit type-space as much as is practical, and wrappers of most <cmath> functions are provided to enable operating solely in the unit_t domain.

The primary purpose of unit containers is to provide type safety and dimensional analysis for mathematical operations. for instance, the velocity of an object can be calculated:

auto objectVelocity = meter_t(100.0) / second_t(2.0);

The resulting velocity type will be deduced to be velocity::meters_per_second with a value of 50.0. Additionally, if the return type if specified, the type system will verify that the units are compatible. For example, the following will fail to compile:

``cpp units::velocity::meters_per_second objectVelocity = square_meter_t(100.0) / second_t(2.0); // Error: Unit types are not compatible.

1 Unit containers can (and should!) be used to perform implicit conversions:
2 
3 ```cpp
4 units::time::second_t a;
5 units::time::minute_t b(1.0);
6 
7 a = b; // a == 60.0

Arithmetic can be performed on unit containers the same way it can for built-in types. However, unlike built-in types, the return value of unit-type arithmetic will be the proper unit to represent the resulting quantity.

using namespace units::length;
using namespace units::area;
meter_t a_m(1.0), b_m(2.0), c_m;
foot_t a_ft(1.0), b_ft(2.0), c_ft;
c_m = a_m + b_m; // OK. c == 3m
c_ft = a_m + b_m; // OK. resulting 3m is converted to ft.
auto result = a_m + b_ft; // OK. result is `meter_t` (left-most unit)
auto result_sm = a_m * b_m; // OK. result_sm is `square_meter_t`.
auto result_s = a_m / b_m; // OK. result_s is `dimensionless_t`.
auto result = a_m * b_ft; // OK. result is `square_meter_t` (left-most unit)
auto result = a_m * square_meter_t(1.0); // OK. units can always be multiplied. Result is `cubed<meter_t>`.
auto result = a_m * scalar_t(1.0); // OK. units can always be multiplied. Result is `meter_t`.

Unsupported arithmetic, or improper return types will result in compiler errors:

c_m = a_m + 5.0; // Error. can't add scalars to dimensioned units.
c_m = a_m + scalar_t(5.0); // Error. can't add scalars to dimensioned units.
auto result = a_m + square_meter_t(1.0); // Error. Incompatible units.

By providing explicit return types for unit functions, the compiler can be used to verify the accuracy of the dimensional analysis, and thus avoiding costly errors.

Unit Literals

If you are using a compiler which supports user-defined literals (e.g. not Visual Studio 2013), then unit literals can be a convenient way to initialize and work with unit values:

using namespace units::literals;
meter_t dist = 10_m; // 10 m
meter_t dist2 = 1_km; // 1000 m

Literals can also be used for any temporary values in calculations, making them more readable:

auto area = units::length::meter_t(5) * units::length::meter_t(10); // without literals
auto area = 5_m * 10_m; // with literals

All literals* are defined by their SI abbreviation preceded by an underscore, e.g. _m for meter. "Square" units are preceded by _sq, e.g. _sq_m for square meters. Non SI units use their most common abbreviations.

All literals are defined in the units::literals namespace, and in order to use literals in your code ***you must include the line using units::literals*** (since there is no way to put a namespace on an operator).

_* with the exception of Teslas, which use _Te for compatibility with MSVC compilers._

<cmath> Functions

The units library include type-safe unit_t container wrappers for almost all of the <cmath> functions, including the c++11 extensions. These functions can be found in the units::math namespace. The units library versions don't conflict with <cmath>, and it's possible to use both libraries in the same code.

The overloaded functions ensure that only the proper unit types are accepted into the functions, and that the return value type matches the expected units, all without needing to result to the type-unsafe toDouble() member.

In rare cases, the overload resolution for a given type may be ambiguous. If so, simply prepend the function with the fully-qualified units::math prefix, e.g.

meter_t x(2.0);
meter_t y(3.0);
square_meter_t z(1.0);
square_meter_t result;
result = fma(x, y, z); // Error: ambiguous
double result = fma(x.toDouble(), y.toDouble(), z.toDouble()); // Warning: Unsafe!
result = math::fma(x, y, z); // OK.

Exponentials and Square Roots

Many functions require units to be raised to some power. This can be accomplished using the units::math::pow function:

square_meter_t m2 = units::math::pow<2>(meter_t(5.0)); // m2 == 25.0

The only constraint is that the exponential power (given in the template argument) must be known at compile time, so that the type system can deduce the output type. This differs from the <cmath> pow implementation, which takes exponent values at runtime.

Square roots are also provided with the units::math::sqrt function. Due to the nature of the sqrt operation, the units library can often provide exact conversions for square root operations, but not in every case. The rest of the time, the sqrt unit will be a rational_approximation of the real value. These are guaranteed to be accurate to at least 10 decimal places.

meter_t m = units::math::sqrt(square_meter_t(4.0)); // m == 2.0

Removing type safety

When interfacing with APIs, libraries, and frameworks which aren't unit enabled, it may be necessary (if regrettable) to remove the type-safety of a unit container and expose its underlying type. This is possible using the unit_cast function, or the to<> member function.

using namespace units;
using namespace units::length;
// Get double value from a unit container (double is the default underlying type of the units library)
meter_t dist(10);
double dval = unit_cast<double>(dist);
double dval2 = dist.to<double>();
// Get integer value (potentially narrowing, be careful!)
int ival = unit_cast<int>(dist);
int ival2 = dist.to<int>();

Both functions produce the same results, the choice of syntax is simply a user preference.

To determine the underlying type of the unit container, the (verbose) trait units::traits::unit_t_traits<decltype(dist)>::underlying_type could be used.

Efficiency

Complex, recurively-defined conversions are performed in just 5 instructions:

    year_t twoYears(2.0);
    week_t twoYearsInWeeks = twoYears;
00007FF7BDB57FF6  xorps       xmm9,xmm9  
00007FF7BDB57FFA  cvtsi2sd    xmm9,rax  
00007FF7BDB57FFF  mulsd       xmm9,mmword ptr [__real@4000000000000000 (07FF7BDBB31A0h)]  
00007FF7BDB58008  divsd       xmm9,mmword ptr [__real@401c000000000000 (07FF7BDBB33C0h)]  
00007FF7BDB58011  movsd       mmword ptr [rbp+6Fh],xmm9  
    EXPECT_EQ(week_t(104.286), twoYearsInWeeks);
00007FF7BDB58017  ...

In the library, the year to week conversion is defined in terms of years -> days -> hours -> minutes -> seconds -> minutes -> hours -> days -> weeks but the total conversion ratio is computed at compile-time and the math is optimized to two floating-point operations.

Unit conversions between equivalent types are optimized away completely, and generate no machine code.

Pure Compile-time Unit Manipulation

In many cases, unit equations are used to determine derived values from a set of values which are known at compile-time. In these situations, it would be optimal to pre-compute the derived values at compile time, thus generating no machine code and incurring no run-time penalty.

The unit_value_t class is the mechanism in the units library to perform compile-time arithmetic. The unit_value_t class functions exactly the same way as std::ratio, but with an associated unit tag and the ensuing type safety.

For a simple example, let's define a right triangle whose hypotenuse is the sum of the squares of its side (a Pythagorean triple)

struct RightTriangle
{
using a = unit_value_t<meters, 3>;
using b = unit_value_t<meters, 4>;
using c = unit_value_sqrt<unit_value_add<unit_value_power<a, 2>, unit_value_power<b, 2>>>;
};

The definition above is perfectly efficient, as it generates no run-time code whatsoever, and still provides all the type safety of unit containers. The values of a, b, and c can be accessed at runtime using the static value() method of unit_value_t

``cpp auto a = RightTriangle::a::value(); // a ismeter_t(3) auto b = RightTriangle::b::value(); // b ismeter_t(4) auto c = RightTriangle::c::value(); // c ismeter_t(5) ``

The available compile-time operations are:

Conversion without unit containers

The preferred method of conversion is implicitly though the use of unit containers, however unit conversion can be accomplished using units::convert for arithmetic types:

double val_in = convert<feet, inches>(1.0); // val_in == 12.0

For type-safe conversion, prefer implicit conversion via unit_t type containers..

Namespaces

Unit tags and containers are split into separate namespaces to avoid conflicting unit names which represent different physical quantities.

Unit tag and unit_t container definitions are defined in the following namespaces:

Literal values for unit containers are defined in the literals namespace

Mathematical operations like sin, log, floor, etc are defined in the following namespaces:

Type traits that you can use to test unit types are defined in the following namespaces:

Defining new units

The units library strives to provide built-in types for every conceivable unit, and before defining your own units you should double-check the namespaces to make sure it's not already included. That said, if you need to roll your own units, the library is extensible by design.

Defining new units is simple, as they can be recursively defined as ratio of previously-defined units in a way that mimics natural language and is highly readable:

namespace time
{
using minutes = units::unit<std::ratio<60>, seconds>;
using hours = units::unit<std::ratio<60>, minutes>;
using days = units::unit<std::ratio<24>, hours>;
using weeks = units::unit<std::ratio<7>, days>;
using years = units::unit<std::ratio<365>, days>;
}

Units are defined in the form: using [unit] = unit<std::ratio<[number of base units per unit]>, [base unit]>;, where:

Compound units are defined in a similar manner, with additional helper functions for polynomials:

using acceleration = compound_unit<meters, inverse<squared<seconds>>>;      // (m / s^2)

The available helpers are:

Unit definition macros

Version 2.1.0 of the units library simplifies the task of adding new units by introducing a set of macros for unit definitions:

UNIT_ADD(length, foot, feet, ft, unit<std::ratio<381, 1250>, meters>)

Would create the units::length::feet tag, the units::length::foot_t container type, and the _ft literal.

UNIT_ADD_DECIBEL(power, watt, dBW)

Adds the dBW_t container, and the _dBW literal.

Adds the units::traits::is_length_unit trait.

Unit Type Traits

The units library provides a comprehensive set of type-traits, which can be used in templated user code to enforce that the unit types have certain properties.

For example, let's say you want to write a function that validates that the square footage of an office (given in any units), meets the minimum size required by local ordinance.

template<typename Units>
bool isMinimumSize(Units x)
{
return x >= square_feet_t(80.0);
}

This function will fail to compile if Units is not a unit of area (since incompatible unit types are not comparable), but it will produce a series difficult-to-understand template errors. Type traits could be used to make the error message more friendly:

template<typename Units>
bool isMinimumSize(Units x)
{
static_assert(units::traits::is_area_unit<Units>::value, "Input value x must represent an area quantity.");
return x >= square_feet_t(80.0);
}

See the units::traits namespace for a list of all the supported traits.

Changing the underlying type of unit_t

The default underlying type for all unit containers is double. However, this can be overridden by providing a definition for UNIT_LIB_DEFAULT_TYPE, e.g.

// Use 64-bit integers as the underlying unit type
#define UNIT_LIB_DEFAULT_TYPE int64_t
#include <units.h>

**_NOTE:_ changing the underlying type may result in unexpected behavior.** Unit conversion makes heavy use of division, which may make integral types unsuitable except for niche embedded applications. Using excessively large types may increase the number of arithmetic overflow errors.

Disabling IOStream

For some embedded applications, it may be desirable to remove all references to <iostream> in order to reduce compiled binary size and RAM requirements. There are two ways to accomplish this:

  1. If you are copy/pasting units.h into your project include directory, then simply define UNIT_LIB_DISABLE_IOSTREAM before including the header.
#define UNIT_LIB_DISABLE_IOSTREAM
#include <units.h>
  1. If you are including units in your project as a CMake target (using add_subdirectory), then all you need to do is set the DISABLE_IOSTREAM cache option, either using the cmake-gui, or by adding the option to the cmake command line during configuration:
1 cmake -DDISABLE_IOSTREAM=ON -DBUILD_TESTS=OFF ..
2 cmake --build . --config Release

Macro clashes

With certain compilers, it is possible that system header files like <ctype.h> will define macros which conflict with the unit literals, which use SI abbreviations. In these cases, it is general safe and advisable to #undef the offending macros.

Windows macros

_T is known to conflict, but is hardcoded into the compiler and can't be disabled. For this reason, tesla units use the _Te abbreviation.

The following macros may need to be undefined on the Windows platform to use units:

#undef pascal
#include <units.h>

ARM macros

The following macros may need to be undefined on the ARM platform to use units::literals:

#undef _U
#undef _L
#undef _N
#undef _S
#undef _P
#undef _C
#undef _X
#undef _B
#define UNIT_LIB_DISABLE_IOSTREAM // it's prudent to disable IOStream on embedded platforms as well.
#include <units.h>

It's best to undefine macros on an as-needed basis.

CMake Instructions

There are several ways to incorporate units.h into your project. The simplest is to just copy include/units.h into your project include directory (which the licensing allows you to do). However, you'll have to properly set up the necessary compilation flags for C++14 (-std=c++14 on gcc).

However, if you are already using CMake as your build system, the recommended way to include units is to copy the entire units project as a subdirectory within your own top-level project folder. Then, in your CMakeLists.txt file add

1 add_subdirectory(units)
2 
3 add_executable(${PROJECT_NAME} main.cpp)
4 target_link_libraries(${PROJECT_NAME} units)

The include path properties are part of the units target, so adding it as a subdirectory and linking against it is all you need to do, no need to worry about additional include directories.

If you don't care about the unit tests, you can minimize compile time by invoking CMake with the following option:

1 cmake -DBUILD_TESTS=OFF ..
2 cmake -build .

Build Instructions

The library itself consists of a single header units.h, and can be included into your project without being built.

The unit tests and documentation can be built with CMake. A doxygen installation is required to generate the documentation, and a Tex install is needed if pdf documentation is desired.

To build the tests:

Windows

  1. Ensure cmake is installed, and that the bin directory is in your PATH% variable, and that a compiler like Visual Studio 2015 Community Edition is installed.
  2. clone the repository or download the .zip package.
  3. Open a cmd terminal and navigate to the source directory.
  4. Type the following commands:
    • md build
    • cd build
    • cmake -Wno-dev ..
    • cmake --build . --config Release
  5. The tests will be created in an executable called unitLibTest.exe in the folder build/unitTests/Release.

Linux

  1. Ensure you are using cmake 3.2 or later. You can verify this with cmake --version.
  2. Ensure you are using gcc version 4.9 or greater. You can verify this with gcc --version.
  3. clone the repository or download the .tar.gz package.
  4. Open a terminal and navigate to the source directory.
  5. Type the following commands:
    • mkdir build
    • cd build
    • cmake -Wno-dev ..
    • cmake --build . --config Release
  6. The tests will be created in an executable called unitLibTest in the folder build/unitTests.

Previous Releases