Skip to content
/ SI Public

A header only C++ library that provides type safety and user defined literals for physical units

License

Notifications You must be signed in to change notification settings

bernedom/SI

Folders and files

NameName
Last commit message
Last commit date

Latest commit

f2bcf57 · Jul 8, 2019
Apr 6, 2019
Apr 17, 2019
Jun 27, 2019
Jun 27, 2019
Jun 27, 2019
Apr 8, 2019
Jul 8, 2019
Jul 8, 2019
Jul 8, 2019
Apr 13, 2019
Mar 13, 2019
Jul 8, 2019
Apr 13, 2019
Mar 1, 2019
Mar 1, 2019
Apr 18, 2019
Apr 18, 2019

Repository files navigation

Build Status GitHub Releases GitHub license Codacy Badge Language grade: C/C++

SI - Type safety for physical units

A header only c++ library that provides type safety and user defined literals for handling pyhsical values defined in the International System of Units.

An illustrative example:

#include <SI/electric_charge.h>
#include <SI/mass.h>

using namespace SI::literals;

constexpr auto one_kilogramm = 1.0_kg;
constexpr auto ten_coulomb = 5.0_A * 2.0_s;
constexpr auto half_an_ampere = ten_coulomb / 20.0_s;

void calculate_mass(const SI::kilo_gram_t<long double> &kg) {
  // do something meaningful here
}

int main(int, char **) {
  calculate_mass(one_kilogramm);
  return 0;
}

SI provides conversions and arithmetic operations with values of any of the International System of Units with strong type safety at compile time. All units are special typedefs of the templated struct SI::unit_t. Only the value of the unit is stored internally, the ratio (i.e. milli, micro, kilo...) is determined as a type trait to allow all units to have the same resolution across the whole implemented ratios. SI handles operations of units of the same ratios as well as when the ratios are different. Operations of between units of the same ratio are overhead-free, else there is additional computation cost to adjust the values to the units.

It is possible to supply custom ratios to the built-in types and they are fully compatible for calculation with other units. However the necessary literals or typedefs have to be supplied by the user. For instance SI::velocity_t<double, std::ratio<1000, 36>> would be "kilo meter per one-hundreth-of-an-hour".

SI Base units

For each Unit the available literals are the implemented ratios prefixed with an underscore. i.e. _mm. _km. Generally the ratios follow metric prefixes of the internation system of units The typedefs are prefixed (or in rare cases interfixed) with the standard metric prefixes. i.e. meter_t, milli_meter_t, kilo_meter_t. The prefix or interfix is marked with an * in the tables below.

Unit Dimension Symbol Unit Symbol implemented ratios unit typedefs
Length L m 10-18 to 1018 *_meter_t
Time T s 10-18 to 100 and 60/1, 3600/1 *_seconds_t, minutes_t, hours_t
Mass* M kg 10-15 to 103 *_gram_t , ton_t
Electric current I A 10-18 to 1018 *_ampere_t
Thermodynamic temperature** t K 10-18 to 1018 *_kelvin_t
Amount of substance N mol 10-18 to 1018 *_mol_t
Luminousity J cd 10-18 to 1018 *_candela_t

* for mass the base ratio is kg (not g) as it is defined in the SI unit table. So there is a mismatch between the literal prefix and the internal representation.

** The dimension symbol for thermodynamic temperature should be Θ (Theta) but the current implementation does not allow for non-ASCII symbols or multi-char symbols

Special Units

Unit Dimension Symbol Exponent Unit Symbol implemented ratios unit typedefs
Area L 2 m2 10-18 to 1018 square_*_meter_t
Volume L 3 m3 10-18 to 1018 cubic_*_meter_t
Frequency T -1 Hz 10-18 to 1018 *_hertz_t
Angle* r 1 rad 10-18 to 1 *_radiant_t
Solid Angle* R 1 sr 10-18 to 1 *_sterradiant_t

* Angle and solid angle are simple containers, not containing any functionality to do angle/room-angle computation such as an overflow after 2*pi.

Derived units with special names

All units that can be built from other units are also decayable to the respective units by inversing the mathematical operation. I.e if Q = I * T then Q / I = T and Q / T = I

Unit Dimension Symbol Unit Symbol builable from implemented literals unit typedefs
Velocity v m/s L / T none none
Acceleration a m/s^2 v / T none none
Electric charge Q C I * T aC to EC *_coulomb_t
Electric potential U V P / I, E/Q aV to EV *_volt_t
Electric resistance O* Ohm (Ω) U / I aOhm to EOhm *_ohm_t
Electric conductance G S I / U aS to ES *_siemens_t
Electric capacity C F Q / U aF to EF *_farad_t
Force F N M * a aN to EN *_newton_t
Pressure p pa F / L^2 apa to Epa *_pascal_t
Energy E J F * L, p * L^3 aJ to EJ *_joule_t
Power P W E/T aW to EW *_watt_t
Magnetic Flux f* Wb U * T aWb to EWb *_weber_t
Magnetic Field B T f/L^2 aT to ET *_tesla_t
Inductance l H f / I aH to EH *_henry_t
Luminous flux m** lm J * R alm to Elm *_lumen_t
Illuminance i* lx m / a alx to Elx *_lux_t
Radioactivity A Bq aBq to EBq *_becquerel_t
Absorbed Dose D Gy aGy to EGy *_gray_t
Equivalent Dose H Sv aSv to ESv *_sievert_t
Catalytic activity K kat N / T akat to Ekat *_katal_t

* These dimensions do not yet have the correct symbols, because the current implementation does not allow for non-ASCII symbols or multi-char symbols. The dimension symbol for electric resistance should be Ω (Ohm) and for magnetic flux Φ (Phi)but. Illuminace should be Eb.

** luminous flux should be Φv which is even more less supported than Φ (Phi) itself.

Building & compatibility

SI is a header only libary that uses C++17 features. Building is tested using cmake > 3.5 and verified for g++7, g++8, clang5, clang6, clang7, msvc 19, and AppleClang 10.0.

to build use

mkdir build
cd build
cmake ..
cmake --build . --config Debug -- -j $(nproc)

substitute --config Debug with --config Release for optimized builds

Installing

To install SI use the commands below: this will install SI into /usr/local/lib/cmake/SI

mkdir build && cd build 
cmake ..
cmake --build . --config Release --target install -- -j $(nproc)

Consider running the build/install command with setting the install prefix, if you do not want to install SI system wide

-DCMAKE_INSTALL_PREFIX:PATH=${HOME}/SI-install

The folder example contains a standalone sample program to check for succesful installation.

Installing using cpack

To build the cpack package use:

mkdir build && cd build
cmake ..
cmake --build . --config Release --target package -- -j $(nproc)

This creates gzipped archives containing all files as well as an installation script SI-<version>-<plattform>.sh.

cd build
mkdir ${HOME}/SI-install
./SI-1.0.1-Linux.sh --prefix=$HOME/SI-install --skip-license --exclude-subdir

Building the tests

The tests use Catch2 version 2.7 which relies on libstdc++8

For ubuntu releases < 18.04 use:

sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt update
sudo apt install libstdc++-8-dev

A word on testing

I'm using more or less strict TDD for implementing the functionality. First to check if the code actually does what I want it to do, but also as a way to set examples how SI is used. The nice benefit of it being, that I'm dogfooding the library to myself while developing. I'm using Catch2 as a unit-testing framework, however since the goal is to be able to do as much as possible during compile time most of the tests are performed with Catch2 STATIC_REQUIRES which contatain static_asserts and run-time REQUIREs as testing assert.

This means if the tests compile then the tests are correct. To compile only with runtime check pass -DCATCH_CONFIG_RUNTIME_STATIC_REQUIRE to the compilers.