All Projects → gitsbi → unlib

gitsbi / unlib

Licence: BSL-1.0 License
a light-weight, header-only, dependency-free, C++14 library for ISO units

Programming Languages

C++
36643 projects - #6 most used programming language
CMake
9771 projects

Projects that are alternatives of or similar to unlib

Insect
High precision scientific calculator with support for physical units
Stars: ✭ 2,469 (+8717.86%)
Mutual labels:  units, quantities
physikal
Mirror of Gitlab Repository
Stars: ✭ 33 (+17.86%)
Mutual labels:  units
Unit Api
Units of Measurement API
Stars: ✭ 140 (+400%)
Mutual labels:  units
Unchained
A fully type safe, compile time only units library.
Stars: ✭ 70 (+150%)
Mutual labels:  units
Dimensioned
Compile-time dimensional analysis for various unit systems using Rust's type system.
Stars: ✭ 235 (+739.29%)
Mutual labels:  units
metric
This library provides zero-cost dimensional analysis for safe, unit-aware numeric computations in Rust.
Stars: ✭ 23 (-17.86%)
Mutual labels:  dimensional-analysis
Mathjs
An extensive math library for JavaScript and Node.js
Stars: ✭ 11,861 (+42260.71%)
Mutual labels:  units
millify
Convert long numbers to pretty, human-readable strings
Stars: ✭ 119 (+325%)
Mutual labels:  units
measured
Type-safe, intuitive units of measure
Stars: ✭ 81 (+189.29%)
Mutual labels:  units
units
A lightweight compile-time, header-only, dimensional analysis and unit conversion library built on c++11 with no dependencies
Stars: ✭ 17 (-39.29%)
Mutual labels:  dimensional-analysis
units
A run-time C++ library for working with units of measurement and conversions between them and with string representations of units and measurements
Stars: ✭ 114 (+307.14%)
Mutual labels:  physical-units
Rink Rs
Unit conversion tool and library written in rust
Stars: ✭ 242 (+764.29%)
Mutual labels:  units
Humanizer.Js
Humanizer meets all your JavaScript needs for manipulating and displaying strings, dates, times, numbers and quantities
Stars: ✭ 71 (+153.57%)
Mutual labels:  quantities
fp-units
An FP-oriented library to easily convert CSS units.
Stars: ✭ 18 (-35.71%)
Mutual labels:  units
UnitfulAstro.jl
An extension of Unitful.jl for astronomers.
Stars: ✭ 18 (-35.71%)
Mutual labels:  units
Safe Units
Type-safe TypeScript units of measure 👷📏
Stars: ✭ 137 (+389.29%)
Mutual labels:  units
prettysize-rs
Pretty-print file sizes and more
Stars: ✭ 29 (+3.57%)
Mutual labels:  units
number-format
Wrapper above number_format and unit convertor.
Stars: ✭ 18 (-35.71%)
Mutual labels:  units
DustryX
More more content for you
Stars: ✭ 24 (-14.29%)
Mutual labels:  units
ShoppingList
📝 My first Android app, a shopping list app.
Stars: ✭ 17 (-39.29%)
Mutual labels:  quantities

unlib

The unlib is a minimal, header-only, C++14-compatible SI unit library, providing quantities that behave like arithmetic types and feature physical dimensions (e.g. power), scaling (e.g., kilo), and tagging of units. If your code has to deal with physical units, you can use this library so that the compiler checks your usage of dimensions and your formulas at compile-time.

The library tries to protect you from Murphy's doings and therefore errs on the side of requiring you to be explicit, rather than on the side of conveniences, where a design decision between these two had to be made.

Getting Started

Installations

The unlib is just a few of headers and the unit tests. If you are not interested in the latter, you can drop the headers into a folder named unlib that's in your path. However, you can also us it as a submodule in your git repository. It comes with a CMakeLists.txt file.

Using it in your code

Include <unlib/common.hpp> in your code in order to use the predefined quantities and literals. (<unlib/common.hpp> provides most of what you want when using this libraries. The two exceptions are <unlib/limits.hpp>, which provides a specialization of std::numeric_limits<> for quantities, and <unlib/math.hpp>, which provides a few mathematical functions for quantities.)

If you want to make use of the predefined literal operators, you need to import the content of the unlib::literals namespace into yours.

Then you can use the predefined quantities right away:

using namespace unlib::literals;

unlib::newton<double> force = 1._kg * 9.81_m_per_s2;

In the above example, the right-hand side of the assignment operator calculates the force of earth's gravitational pull in kN which is used to initialize a quantity of the unit N. The conversion of the scale (kilo ==> unscaled) necessary for this is done implicitly during the assignment.

Newton is a unit of the physical dimension force, kilogram one of mass, and meter per second² of acceleration. Knowing this, it is possible to write a generic function calculating the force from mass and acceleration no matter what scaling:

template<typename V, typename MassScale, typename AccelerationScale>
auto force( const unlib::quantity<unlib::mass, MassScale        , V>& mass
          , const unlib::quantity<unlib::mass, AccelerationScale, V>& acceleration ) {
	return mass * acceleration;
}

auto kN = force(1._kg, 9.81_m_p_s2);

Note that in this example, since the mass is provided in kilogram and the result type is auto, no automatic scaling will occur and the result will be a quantity of the unit kN (kilonewton).

Acknowledgments

This library is based on ideas and solutions which melak47 came up with when, at a company we worked together at, he created such a beast for C++03 (which is infinitely harder, mind you). He, in turn, built on previous work done for Boost.Unit.

Detailed documentation

At the moment, the only detailed documentation is the inline documentation in the library's headers.

Comparison to other work

There currently isn't a comprehensive comparisons with other libraries providing similar features, and I lack the time and, frankly, motivation, necessary to delve into those other libraries deep enough to make a comparison chart that's not inherently biased toward my library. However, before I started to write my own, I had looked at various other libraries, and there are a number of reasons why I did not use either of those, but decided to write my own. The main reasons are listed here:

  • Mateusz Pusz' unit library, which he tries to get into the next C++ standard, doesn't provide the C++14 compatibility needed for my work.
  • Boost.Units allows you to create your own unit system, whereas this library only enable you to use one system: SI. This of course greatly reduces the flexibility of unlib compared to Boost.Units, but makes it much easier to use, and very much faster to compile.
  • Nic Holthaus' unit library has a rather unique way to scale units (think kilo, nano) which makes it impossible to, for example, write function templates that take any quantity, regardless of scale. It also doesn't provide any means for equivalent units that must be distinguished, like active, reactive, and apparent power in EE. However, this both were must-have features for my work.

This is, of course, very biased, because it just lists the criteria used to dismiss the unit libraries we looked at in my job. That, however, is all I have at the moment. But if you, or anyone else, FTM, actually know any of those other libraries better than I do (which isn't hard, really), I'd be very interested to hear about more differences.

Main features

Units

The SI unit system is built on the seven base units:

  1. seconds (time)
  2. kilogram (mass)
  3. meter (length)
  4. Ampere (current)
  5. Kelvin (temperature)
  6. mol (substance amount)
  7. candela (luminous intensity)

From these, all other units can be derived.

(Note that this means that unlib does not provide units like, e.g., kilobytes. While in principle it is easy to add other units to this library's system of units, unlib was specifically not designed to be a framework for any unit system. If you need to create your own type systems, you might want to look into Boost.Units.

Note that the unlib, despite claiming to be a unit library, does not actually provide a single type for units. A quantity's unit is expressed through three template parameters of the quantity's type: dimension, scale, and tag.

Dimensions

The base units, and all units which can be derived from them, are measuring units for physical dimensions. Dimensions can be described by the exponents of the underlying seven base units. A dimension, in the sense of this library, therefore is a (compile-time) set of the seven exponents, representing the seven base units. For example, electrical charge, measured in Coulomb, a.k.a. Ampere second, can be defined this way:

using my_dimension = unlib::dimension< /* time               */ unlib::ratio_t<1>  
                                     , /* mass               */ unlib::ratio_t<0> 
                                     , /* length             */ unlib::ratio_t<0> 
                                     , /* current            */ unlib::ratio_t<1> 
                                     , /* temperature        */ unlib::ratio_t<0> 
                                     , /* substance amount   */ unlib::ratio_t<0> 
                                     , /* luminous intensity */ unlib::ratio_t<0> >;

Since the library's way of telling these exponents from each other is their order, they must always be passed in the correct order, which is an invitation to silly errors. Therefore, dimensions can, and should, be built in different, less error-prone, ways. For example, the dimension_t meta function creates dimensions from the exponents of the seven base units, passed in any order. Using it, electrical charge can be defined this way:

using electrical_charge = unlib::mul_dimension_t<unlib::current, unlib::time>;

This is both easier to write and read. If you want to measure the mass of cables, you could use a unit kg/m. This would be its dimension:

using mass_per_length = unlib::div_dimension_t< unlib::mass
                                              , unlib::length >

However, users usually won't have to deal directly with dimensions as they mostly use quantities. But if they do need to use dimensions, then the type of a quantity defines the nested type dimension_type, which could also be used to define this dimension:

using namespace unlib::literals;
auto one_kg_per_meter = 1_kg/1_m;
using mass_per_length = decltype(one_kg_per_meter)::dimension_type;

Scales

Units are scaled, where scales are rational numbers, represented by std::ratio. Due to the limitations of 64 bit integer arithmetic with std::ratio, of the standard scales provided by the SI system, this library only covers femto–exa. They are named like unlib::femto_scaling, unlib::pico_scaling etc., but they are simply aliases for std::femto, std::pico, etc. Besides those, noteworthy pre-defined scales are no_scaling, which denotes and unscaled quantity, as well as minute_scaling, hour_scaling, day_scaling, and week_scaling, which can be used to properly scale time quantities according to the Babylonian scaling system we use for time units.

The library also provides meta functions to create scaled quantities from the unscaled quantities:

using milligram = unlib::milli<unlib::gram>;
using  kilogram = unlib::kilo<unlib::gram>;
using       ton = unlib::kilo<kilogram>;       // humans don't use megagram

Quantities

Quantities hold values in your code and mostly behave like C++' built-in arithmetic types: they can be added, multiplied, compared, etc. However, a quantity's template parameter specify the quantity's unit (kilowatt) and its value type (double), and quantities representing incompatible units or value types are incompatible to each other. The compiler keeps track of the correct types of the results of arithmetic operations. For example, multiplying a quantity of the unit kW (power) with a quantity of the unit h (time) will result in a quantity of the unit kWh (energy), which cannot be assigned to a quantity of the unit kW.

An object of a quantity can be created from a value type explicitly. It cannot be implicitly created from a value type, though:

void f(unlib::quantity<unlib::mass,unlib::kilo,double>);

kilogram kg{42.}; // fine
f(kg);            // fine, too
f(kilogram{42.}); // also fine
f(42.);           // won't compile

using namespace unlib::literals; // needed for unit suffixes
f(42._kg);        // also works

gram g = kg;      // works, too, because scaling conversions are implicit

A quantity also cannot be used where its value type is needed. If you need to pass a quantity to an API that expects one of the built-in value types instead, you have to explicitly convert it:

void f(double kg);

f(some_mass.get());                             // returns underlying value, whatever scaling
f(some_mass.get_scaled<unlib::kilo_scaling>()); // will always return kg 

Values

Any integer or floating point type can be used as a quantity's value type. However, the literal operators provided by the library (in namespace unlib::literals) will return unlib::literals::integer_value_type (an alias for int) and unlib::literals::floatpt_value_type (an alias for double). If you need to use std::int64_t or long double, but also want to use literals, you have several choices:

You could wrap every constant into an explicit cast:

auto distance{unlib::value_cast<long double>(42_m)};

Note that you do not have to explicitly name that cast's target's value type. This works, too:

distance = unlib::value_cast(42_m);

Alternatively, you could provide your own literal operators in a different namespace. However, this library provides hundreds of predefined literal operators, so this is no small feat.

Finally, you could create a pull request that makes the types returned by the library's literal operators compile-time configurable. :-)

Tags

Sometimes, different quantities which must not be confused are represented by the same physical unit. For example, in electrical engineering, when it comes to AC, there is active power, reactive power, and apparent power. All three are units of power and can be represented by the physical unit Watt. Nevertheless, usually they must not be confused. In order to allow this, quantities also have an optional template parameter Tag, which defaults to no_tag. A quantity with the tag no_tag is considered an untagged quantity.

A tag consists of a tag ID and a tag ratio, which are passed to unlib::tag_t. Except for void (which will inevitable turn the tag into an unlib::no_tag), any type, even an incomplete one, can be used for tag IDs. The only significance of these types is that they differ from each other.

The ratio is a std::ratio and is used to keep track of tags when multiplying or dividing tagged types. (If the tag ration is 0 (zero), then the tag's ID will become void, turning the tag into an unlib::no_tag.) Quantities with either differing tag IDs or differing tag ratios are considered to be of different type and cannot be assigned to each other. (There is, however, a cast to circumvent this.)

Tags should be created by passing a tag ID to unlib::tag_t. The tag ratio should be omitted and defaults to 1/1.

using kiloWatt = unlib::quantity<unlib::power, unlib::kilo_scaling, unlib::integer_value_type>;
using kiloVar  = unlib::quantity<unlib::power, unlib::kilo_scaling, unlib::integer_value_type, unlib::tag_t<struct reactive_power_tag_id>>;

kiloWatt W = 42_W;
kiloWatt W2 = W;    // fine
kiloVar kvar = W;   // cannot assign due to different tag

(Note: Using tags, the library already provides the three incompatible quantities watt for active power, var for reactive power, and voltampere for apparent power. Those are, again, templates, which can be parametrized with a value type.)

Two quantities can be added and subtracted if both have the same tag (which can be no_tag). They can be multiplied and divided if

  1. either operand has the no_tag tag or
  2. both operands have the same tag (including no_tag).

If a quantity without a tag (that is, a quantity with the no_tag) is multiplied with a quantity with a tag (other than no_tag), the result will be tagged with the tag (other than no_tag). When multiplying and dividing tagged quantities, the library keeps track of the tag's "exponents" (how many times quantities of the same tag have been multiplied or divided with).

Therefore you can, for example, multiply reactive power with time, resulting in reactive energy. This divided by time results again in reactive power. If it is divided by reactive power, the result will be (untagged) time. You can also square reactive power, resulting in a quantity with a reactive tag exponent of 2/1. If you then divide this by reactive power, the result will be reactive power again.)

Note that tags do not reflect all properties of their engineering counterparts. For example, dividing reactive power by voltage, which is untagged, will have the resulting quantity of current being tagged as reactive, which very likely won't make much sense. In these cases you will have to use a tag_cast to make the library submit to your application domain's rules.

The following example from the domain of AC electrical engineering shows the calculation of apparent power from active and reactive power. Since reactive and apparent power are tagged quantities, and the rules of the application domain do cannot be implicitly expressed using the tagging mechanism, the tags have to be casted:

template<typename ActiveScale, typename ReactiveScale, typename V>
auto apparent_power( const unlib::quantity<unlib::power,   ActiveScale, V>& lhs
                   , const unlib::quantity<unlib::power, ReactiveScale, V, unlib::reactive_power_tag>& rhs) {
	return unlib::sqrt( unlib::pow<2>(unlib::tag_cast<unlib::apparent_power_tag>(lhs))
	                  + unlib::pow<2>(unlib::tag_cast<unlib::apparent_power_tag>(rhs)) );
}

unlib::kilo<unlib::voltampere<double>> = apparent_power(10._kW, 5._kVA);

Conversions

There are four different kind of casts available:

  1. value_cast allows casting between units with different value types, e.g., seconds in int vs. seconds in long long.
  2. scale_cast allows casting between units with different scales, e.g., seconds and minutes. (Note: Quantities that only differ in their scaling can implicitly be constructed from each other, so this cast will rarely ever be needed.)
  3. tag_cast allows casting between units with different tags, e.g., active and reactive power.
  4. quantity_cast allows casting between units where value types, scales, and tags might be different.

All four types of casts come in two flavors. One needs the targeted value type, scale, tag, or quantity to be specified (just like static_cast etc. do):

  unlib::scale_cast<unlib::milli>(any_weight);

The other flavor of the same cast does not need this. It returns a temporary object from which a quantity can be created, and that can be assigned to a quantity. Depending on the quantity created from it, or it is assigned to, the requested conversion will be invoked automatically on assignment:

  my_floating_hours float_hrs = unlib::value_cast(integer_seconds); // note: scale cast from secs to hrs is implicit

However, remember that this cast returns a temporary object which is not a quantity, and must be assigned to a quantity in order to be used. Specifically, it cannot be passed to functions expecting a quantity and therefore cannot be used in mathematical operations:

  // won't compile
  unlib::kilo<unlib::watt> power = unlib::tag_cast(some_reactive_power_in_kW) / some_time;

In these cases, you need to explicitly mention the target you want to cast to, in order to immediately create an actual quantity:

  unlib::kilo<unlib::watt> power = unlib::tag_cast<unlib::no_tag>(some_reactive_power_in_kW) / some_time;

Literals

The library comes with predefined literal operators for the majority of the common quantities it predefines. You can find them in the header <unlib/common.hpp>. In order to be usable, those operators first must be brought into your current namespace through a using directive:

using namespace unlib::literals;

unlib::second<int> s = 1_h;

std::cout << s; // prints 3600

Contributing

If you find bugs or want to request a feature, please create an issue. Of course, I'd be happy to look at a pull request as well.

If you want to talk to me for whatever reason, I am @tweetsbi on Twitter.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].