TU is a C++ header-only library for typesafe unit operations. With TU you create instances of templated structs that represent units and operate on these instead of operating on numbers.
Unit<prefix::milli, second> s(5.0f); Unit<prefix::micro, ampere> a(10.0f); Unit<prefix::no_prefix, coulomb> c = s * a; std::cout << c.value << std::endl; // prints 5e-08If you need a non-SI unit you define it by declaring a Non_coherent_unit. This is how you would define the unit degree_Fahrenheit:
using degree_Fahrenheit = Non_coherent_unit<1.0f / 1.8f, -32.0f, degree_Celsius>;You can use the new unit like any other unit already defined in TU:
Unit<prefix::no_prefix, degree_Celsius> c(0.0f); Unit<prefix::no_prefix, degree_Fahrenheit> f(c); std::cout << f.value << std::endl; // prints 32TU handles prefixes under the hood so you have complete freedom mixing prefixes.
Unit<prefix::milli, second> s1(20.0f); Unit<prefix::micro, second> s2(30.0f); Unit<prefix::nano, second> s3 = s1 + s2; std::cout << s3.value << std::endl; // prints 2.003e+07Attempts to initialize or operate on incompatible units will result in compilation failure.
Unit<prefix::milli, second> s(20.0f); Unit<prefix::micro, ampere> a(10.0f); auto sa = s + a; // compilation failure Unit<prefix::micro, ampere> a2 = s * a; // compilation failureCurrent supported typesafe operations on units are:
- Addition (+)
- Subtraction (-)
- Multiplication (*)
- Division (/)
- Power to arbitrary floating point number (pow)
- Square root (sqrt)
- Comparison <, >, <=, >=, !=, ==.
- Unary operations on scalar units (e.g trigonometric function like
std::sin), - Unit conversion (e.g. mK (milli Kelvin) to °F (degrees Fahrenheit))
By default TU uses single precision (float) as the underlying data type. To use double precision (double), assign double to the macro TU_TYPE i.e include #define TU_TYPE double before the inclusion of typesafe_units.h. If you use CMake, the definition can be made by
target_compile_definitions(my_target PRIVATE TU_TYPE=double)TU requires a c++20 compliant compiler. For the test suite that comes with TU to work, your system needs to have support for ANSI escape sequences since the output uses colours. This should work on fairly recent Windows 10 system, linux and macOS. It might be a problem on Windows 7 though. If you find that this is a showstopper for you please let us know. If enough people run TU on systems that does not have support for ANSI escape sequences, we will remove it.
TU is continuously built on Windows and Linux (Ubuntu) with MSVC and GCC respectively. For exact versions of tested compilers, please see the build logs of the github ci builds.
TU is a header-only library. To use TU in you project, simply include the header typesafe_units/include/tu/typesafe_units.h.
If you want to use TU as a CMake package you can use the CMake command find_package as follows and include the header by #include "tu/typesafe_units.h"
# # The package is called TU. Include it with `find_package`. # If CMake does not find the package you can specify the path to the TU root as # a HINT. You are required to state the exact version of TU that you want to # use. <version> should be given on the format major.minor.patch e.g. 1.2.3. # find_package(TU <version> REQUIRED HINTS "<absolute path to TU root>") # # The library itself is called `tu` (lowercase). Link your target to it. # target_link_libraries(my_target tu) # # Make some configurations. # TU_TYPE sets the underlying datatype of TU. Use float or double. # set_property(TARGET my_target PROPERTY CXX_STANDARD 20) target_compile_definitions(my_target PRIVATE TU_TYPE=<float, double>)TU comes with its own test suite. It does not rely on any external testing tool. To verify that TU runs on your system, build the test suite with CMake.
The following instruction assumes that you do an out of source build in a directory under the repository root.
> cmake .. -G <generator> > cmake --build . --config <build type> Run the test suite
> ctest -VThe test suite test TU for both float and double as underlying datatype.
The aim of TU is to be
- compliant to definitions and guides of official bodies. For SI units, TU aims for compliance with the definitions issued by Bureau International des Poids et Mesures (BIPM). See bimp.org for details.
- (type)safe
- easy to use
- light weight
TU is released under the MIT license.
TU uses official spelling of units. Therefore TU uses litre and metre and not liter and meter.
The intrinsic data type used by TU is defined in the preprocessor macro TU_TYPE. TU_TYPE can be float or double. All values will have the type defined by TU_TYPE.
The main namespace of TU is tu. Functionality inside tu that is located in the namespace internal is not public and should only be used implicitly through public classes and methods.
The TU unit system is built on five structs: Base_unit, Coherent_unit, Non_coherent_unit, Unit and the enum struct prefix.
The illustration below shows how the different structs are used to create other structs. Structs at lower level uses structs on higher level in their construction.
prefix Base_unit | | | Coherent_unit | | | | | Non_coherent_unit | | | | | Non_coherent_unit | | | |--------Unit With words the above would be written:
Coherent_unitis built fromBase_unit(s)Non_coherent_unitis built from aCoherent_unitor anotherNon_coherent_unitUnitis built from aprefixand aCoherent_unitor aNon_coherent_unit.
The main entity that a typical user of TU will interact with is the Unit struct. When extending the unit system, interaction with other structs is required. At some occasions interaction with Coherent_units is necessary.
Base units are the smallest building blocks in the types system. These define powers of the seven basic units s, m, kg, A, K, mol and cd. Base units are used to build up Coherent_units.
The definition of base units are done through inheritance of the Base_unit struct and looks as follows where the base unit is denoted X.
template<Ratio p> struct X : internal::Base_unit<p>{};The definition of s (second) to some power p then looks as
template<Ratio p> struct s : internal::Base_unit<p>{};where Ratio is of type std:ratio
and a base unit per_second can be declared through
s<std::ratio<-1>;The Coherent_unit struct represents a unit that is a multiple of all base units: s, m, kg, A, K, mol and cd with individual powers. A specific coherent unit should be defined by inheriting from a Coherent_unit
The specific coherent unit newton is defined as
using newton = Coherent_unit<s<std::ratio<-2> ,m<std::ratio<1> ,kg<std::ratio<1> ,A<std::ratio<0> ,K<std::ratio<0> ,mol<std::ratio<0> ,cd<std::ratio<0>>;i.e. it has the unit kg m / s^2
Note that computations using seconds should use the Coherent_unit second and not the base unit s.
second is defined as
using second = Coherent_unit<s<std::ratio<1> ,m<std::ratio<0> ,kg<std::ratio<0> ,A<std::ratio<0> ,K<std::ratio<0> ,mol<std::ratio<0> ,cd<std::ratio<0>>;All base units are defined as Coherent_units in similar fashion.
A Non_coherent_unit is a unit that is scaled or shifted relative to a base unit. The value of the Non_coherent_unit is related to the value of the base unit through v = a * b + c where v is the value of the Non_coherent_unit, b is the value of the base unit. a and c are the scaling and shift respectively.
Examples of Non_coherent_units are minute, hour and degree_Celcius.
A Non_coherent_unit is a templated struct that has the scaling factor, the shift and the base unit as template parameters.
minute and hour are defined by
using minute = Non_coherent_unit<(TU_TYPE)60.0, (TU_TYPE)0.0, second>; struct hour = Non_coherent_unit<(TU_TYPE)60.0, (TU_TYPE)0.0, minute>;degree_Celsius is defined by
struct degree_Celsius = Non_coherent_unit<(TU_TYPE)1.0, (TU_TYPE)273.15, kelvin>;The Unit is the intended public unit type. It is a templated struct with a prefix and a unit as template parameters.
A unit variable, u, is declared by
Unit<prefix, unit> u;where prefix is one of the prefix types defined in the enum struct prefix and unit is a Coherent_unit or a Non_coherent_unit.
A Unit can be constructed from a value of type TU_TYPE or from another unit of the same type of unit.
A unit representing 5 nano seconds is created by
Unit<prefix::milli, second> ms(5);This can in turn be used to create a new unit variable.
Unit<prefix::no_prefix, minute> mi(ms)The values of ms and mi are obtained through the value member.
std::cout << ms.value << " " << mi.value << std::endl; // prints 5.0 8.3333e-5 The following prefixes are defined and can be used when creating Units.
- quecto = 10-30
- ronto = 10-27
- yocto = 10-24
- zepto = 10-21
- atto = 10-18
- femto = 10-15
- pico = 10-12
- nano = 10-9
- micro = 10-6
- milli = 10-3
- centi = 10-2
- deci = 10-1
- no_prefix = 100
- deca = 101
- hecto = 102
- kilo = 103
- mega = 106
- giga = 109
- terra = 1012
- peta = 1015
- exa = 1018
- zetta = 1021
- yotta = 1024
- ronna = 1027
- quetta = 1030
The convert_to function converts one unit variable to a different unit (of same basic type)
It is defined by
template<prefix to_prefix, typename To_unit, prefix from_prefix, typename From_unit, template<prefix, typename> typename Unit> requires std::is_same<typename From_unit::Base, typename To_unit::Base>::value Unit<to_prefix, To_unit> convert_to(const Unit<from_prefix, From_unit>& from) noexceptAn example of usage could be
Unit<prefix::no_prefix, Minute> m(1.0f); std::cout << tu::convert_to<prefix::milli,Second>(m).value << std::endl; // prints 60000.0TU supports the binary operators + and - (addition and subtraction) on units. Conversions are handled under the hood of TU.
Unit<prefix::no_prefix, minute> mi(5.0f); Unit<prefix::no_prefix, hour> h(1.0f); Unit<prefix::milli, second> ms = h + mi; std::cout << ms.value << std::endl; // prints 3.9e6Note that the result of the + and - operators on units is not a Unit but a Coherent_unit. If we instead would do
Unit<prefix::no_prefix, minute> mi(5.0f); Unit<prefix::no_prefix, hour> h(1.0f); auto cu = h + mi; std::cout << cu.base_value << std::endl; // prints 3900.0This is because TU does not know what Unit to construct from the operation. TU falls back on the fundamental Coherent_units and cu will be of type
Coherent_unit<s<std::ratio<1> ,m<std::ratio<0> ,kg<std::ratio<0> ,A<std::ratio<0> ,K<std::ratio<0> ,mol<std::ratio<0> ,cd<std::ratio<0>>;Note also that Coherent_unit does not have a value member but only a base_value
If we would like a specific Unit representation of the operation, we have to explicitly state the Unit as in the first example and the result of the operation will be used to construct the desired Unit.
Applying the + and - operators on Units that don't have the same underlying Coherent_unit will result in compilation failure e.g. it is not possible to add two variables of type newton and second.
TU supports the binary operators * and / (multiplication and division).
Unit<prefix::milli, second> s(5.0f); Unit<prefix::micro, ampere> a(10.0f); Unit<prefix::micro, coulomb> c = s * a; std::cout << c.value << std::endl; // prints 5e-02Note that the result of the * and / operators on units is not a Unit but a Coherent_unit. If we instead would do
Unit<prefix::milli, second> s(5.0f); Unit<prefix::micro, ampere> a(10.0f); auto cu = s * a; std::cout << cu.base_value << std::endl; // prints 5e-08This is because TU does not know what Unit to construct from the operation. TU falls back on the fundamental Coherent_units and cu will be of type
Coherent_unit<s<std::ratio<1> ,m<std::ratio<0> ,kg<std::ratio<0> ,A<std::ratio<1> ,K<std::ratio<0> ,mol<std::ratio<0> ,cd<std::ratio<0>>;Note also that Coherent_unit does not have a value member but only a base_value
If we would like a specific Unit representation of the operation, we have to explicitly state the Unit as in the first example and the result of the operation will be used to construct the desired Unit.
Note that trying to create a Unit that does not have the correct Coherent_unit base would result in compilation failure.
TU implements comparison operators for units with the same underlying Coherent_unit. Comparison is made to the Units base_values so that
Unit<prefix::milli, metre> me1(5.0f); Unit<prefix::no_prefix, metre> me2(0.004f); if (me2 < me1) std::cout << "true as expected"; // prints "true as expected" TU implements a pow operator for units.
Unit<prefix::milli, metre> me(5.0f); auto ch = pow<std::ratio<2>>(me); std::cout << ch.base_value << std::endl; // prints 2.5 * 10^-5ch will be of type
Coherent_unit<s<std::ratio<0> ,m<std::ratio<2> ,kg<std::ratio<0> ,A<std::ratio<0> ,K<std::ratio<0> ,mol<std::ratio<0> ,cd<std::ratio<0>>;To construct a Unit directly we could do
Unit<prefix::milli, metre> me(5.0f); Unit<prefix::milli, metre_squared> m2 = pow<std::ratio<2>>(me); std::cout << m2.value << std::endl; // prints 2.5 * 10^-2Note that Unit<prefix::milli, metre_squared> means 10-3m2 and not (mm)2
To the unit (mm)2 is equivalent to Unit<prefix::micro, metre_squared>
Note that the power is restricted to std::ratio.
The operation
sqrt(unit).is equivalent to
pow<std::ratio<1,2>>(unit).TU supports unary operations on scalar units i.e. units where all basic unit powers are 0. Examples of scalar units is radian and degree.
unop is a template function that applies any unary function that takes a TU_TYPE and returns a TU_TYPE to the underlying base_value of the unit if it is a scalar unit. The function returns a scalar Coherent_unit initialized with the value of the performed operation. This makes it possible to operate with any unary function (subjected to the restrictions above) from the standard library on a Unit or Coherent_unit. unop can take both unary functions and lambda expressions as template parameter.
Unit<prefix::no_prefix, degree> angle_d(90); std::cout << unop<std::sin>(angle_d).base_value; // prints 1 Unit<prefix::no_prefix, radian> angle_r(PI); std::cout << unop<std::sin>(angle_r).base_value; // prints 1 constexpr auto lambda = [](TU_TYPE v) { return v + (TU_TYPE)1.0; }; std::cout << unop<lambda>(angle_d).base_value; // prints 2.5708 i.e. PI/2.0 + 1.0Note that unop operates on the base_value on a unit. In the case of degree the base unit is radian (90 degrees == pi/2 radians) and the std::sin function yields the correct result.
- second
- metre
- kilogram
- ampere
- kelvin
- mole
- candela
- hertz
- becquerel
- ohm
- siemens
- farad
- lumen
- weber
- gray
- sievert
- watt
- newton
- lux
- radian
- joule
- steradian
- katal
- pascal
- coulomb
- henry
- tesla
- volt
- metre_per_second
- second_squared
- metre_cubed
- metre_squared
- minute
- hour
- day
- degree_Celsius
- gram
- tonne
- dalton
- unified_atomic_mass_unit
- electronvolt
- litre
- degree
- arc_minute
- arc_second
- hectare
- astronomical_unit