std.units

Type-based, i.e. statically checked, units of measurement.

A quantity is a wrapper struct around an arbitrary value type, parametrized with a certain unit. A unit is basically little more than a marker type to keep different kinds of quantities apart, but might additionally have an associated name and symbol string, and – more more importantly – define conversions to other units. While all of the possible conversions must be defined statically for type-checking, arbitrary callables are supported for actually converting the values, which do not necessarily need to be evauatable at compile time.

Conversions only happen if explicitly requested and there is no different internal representation of values – for example 1 * kilo(metre) is stored just as 1 in memory, not as 1000 or relative to any other »canonical unit«.

On top of the template core of the module, to which units are types only, a layer making use of »dummy« unit instances with operators defined on them makes it possible to work with quantities and units in a natural way, such that the actual unit types never need to be user directly in client code (see the example below).

In the design of this module, the explicit concept of dimensions does not appear, because it would add a fair amount of complication to both the interface and the implementation for little benefit. Rather, the notion is established implicitly by defining conversions between pairs of units – to see if two units share the same dimension, just check for convertibility.

The std.si module defines SI prefixes and units for use with this module.

Example:
 enum foo = baseUnit!("foo", "f");
 enum bar = scale!(foo, 21, "bar", "b");

 auto a = 2 * bar;
 assert(convert!foo(a) == 42 * foo);

Todo:

License:
Boost License 1.0.

Authors:
David Nadlinger

template UnitImpl()
Mixin template containing the implementation of the unit instance operators.

Furthermore, it marks the surrounding type as unit – every unit type has to mixin this template.

In addition, a unit type might also define a toString() function returning a custom unit symbol/name, and a list of Conversions. The example shows how a unit type for inches might be defined if scale and ScaledUnit did not exist (which remove the need to write boilerplate code like this).

Example:
 struct Inch {
     mixin UnitImpl;

     static string toString(UnitString type = UnitString.name) {
         final switch (type) {
             case UnitString.name: return "inch";
             case UnitString.symbol: return "in";
         }
     }

     alias TypeTuple!(
         Conversion!(centi(metre), toCm, fromCm)
     ) Conversions;

     static V toCm(V)(V v) {
         return cast(V)(v * 2.54);
     }
     static V fromCm(V)(V v) {
         return cast(V)(v / 2.54);
     }
 }
 enum inch = Inch.init; // Unit instance to use with abbreviated syntax.

Note:
Two existing units a and c can't be retroactively extended with a direct conversion between them. This is by design, as it would break D's modularization/encapsulation approach (alternative: use mixin template for defining conversion functions, then it would be possible to have different behavior of the conversion function in each module). However, currently it is possible to create a third unit b which is convertible to both a and c, and then perform the conversion in two steps: convert!c(convert!b(1 * a)))

auto opBinary(string op : "*", Rhs)(Rhs rhs);
auto opBinary(string op : "/", Rhs)(Rhs rhs);
Multiplication/division of two unit instances, yielding a unit instance representing the product/quotient unit.

Example:
 enum joule = newton * metre;
 enum watt = joule / second;

auto opBinary(string op : "*", V)(V rhs);
auto opBinary(string op : "/", V)(V rhs);
auto opBinaryRight(string op : "*", V)(V lhs);
auto opBinaryRight(string op : "/", V)(V lhs);
Multiplication/division of an unit and a value type, constructing a Quantity instance.

Example:
 auto a = 2 * metre;
 auto b = 2 / metre;
 auto c = metre * 2;
 auto d = metre / 2;

enum UnitString;
Possible string representations of units.

name
Use full unit names when constructing strings.

symbol
Use unit symbols when constructing strings.

struct BaseUnit(string name,string symbol = null);
template baseUnit(string name,string symbol = null)
Shordhand for creating a basic unit with a name and a symbol, and no conversions defined.

When using BaseUnit, in virtually every use case, you also want to define an associated unit instance, as shown below. As there should be no real use for the unit type in user case anyway, you can also use baseUnit which directly returns a unit instance.

Example:
 alias BaseUnit!("Ampere", "A") Ampere;
 enum ampere = Ampere.init;
 // or
 enum ampere = baseUnit!("Ampere", "A");

struct Dimensionless;
Dimensionless dimensionless;
A special unit used to represent dimensionless quantities.

struct BaseUnitExp(B,R) if (!isDerivedUnit!(B) && isUnit!(B) && isRational!(R));
A pair of a (base) unit and a compile-time rational exponent.

Multiple BaseUnitExps make up a DerivedUnit.

template DerivedUnit(T...) if (allSatisfy!(isBaseUnitExp,T))
struct DUnit(T...);
Constructs a derived unit, consisting of a number of base units and associated exponents.

Usually, constructing unit types using the operators defined on the unit instances is preferred over using this template directly.

Internally, derived units are represented as DUnit instances, but never try to use it directly – the DerivedUnit template performs canonicalization to ensure that semantically equivalent units share the same underlying types. Also, the result will not actually be a DUnit in some cases, but rather Dimensionless or a single base unit without exponent.

Example:
 alias DerivedUnit!(
     BaseUnitExp!(Ampere, Rational!1),
     BaseUnitExp!(Second, Rational!1)
 ) Coulomb;
 enum coulomb = Coulomb.init;

 // In most cases, you would want to just use the operators
 // on the unit instances instead:
 enum coulomb = ampere * second;

struct AffineUnit(BaseUnit,alias toBaseOffset,string name,string symbol = null) if (isUnit!(BaseUnit));
template AffineUnit(alias baseUnit,alias toBaseOffset,string name,string symbol = null) if (isUnitInstance!(baseUnit))
auto affine(alias toBaseOffset, string name, string symbol = null, U)(U u);
An affine unit – the most common case being a unit that is related to other units representing the same physical quantity not by a scale factor, but by a shift in the zero point.

This is not a fundamentally new concept, adding a constant offset could just be implemented in a custom conversion function as well (see the UnitImpl documentation). However, Quantity is specialized on affine units such as to only provide operations which make sense for them:

Informally speaking, an affine space is a vector space which »forgot« its origin, its elements are points, not vectors. Thus, a quantity of an affine unit cannot be added to another (as it makes no sense to add two points), but like vectors can be added to points to yield a new point, a quantity of the underlying base unit can be. Also, two affine quantities can be substracted to yield a quantity of the base unit (just as two points can be substracted to get a vector pointing from one to another).

The most common example for this are units of temperature like degrees Celsius or Fahrenheit, as demonstrated below.

Example:
 enum celsius = affine!(273.15, "degrees Celsius", "°C")(kelvin);
 auto t = 3.0 * celsius;
 t += 1.0 * kelvin; // adding Kelvin is okay
 assert(!__traits(compiles, t += 2.0 * celsius)); // adding Celsius is not
 writeln(t - 0.0 * celsius); // 4 Kelvin, not degrees Celsius

struct Quantity(Unit,ValueType = double) if (isUnit!(Unit));
template Quantity(alias unit,ValueType = double) if (isUnitInstance!(unit))
A quantity consisting of a value and an associated unit of measurement.

The unary plus, unary minus, addition, subtraction, multiplication, division, comparison, increment and decrement operators are forwarded to the underlying value type, if the operation is meaningful for the given unit(s).

A quantity is only implicitly convertible to the underlying value type (via alias this) if it is dimensionless – divide a quantity by its unit if you want to access the raw value.

this(OtherV)(Quantity!(Unit,OtherV) other);
Quantity opAssign(OtherV)(Quantity!(Unit,OtherV) other);
Two quantites of the same unit are implicitely convertible on assignment if the underlying value types are.

Example:
 Quantity!(metre, float) a = 2 * metre;

Quantity!(Unit,NewV) opCast(T : Quantity!(Unit,NewV), NewV)();
A quantity is castable to another one with the same unit if the value type can be casted to the new one.

For converting a quantity to another unit, see convert instead.

Example:
 auto a = 2.0 * metre;
 assert(cast(Quantity!(metre, int))a == 2 * metre);

auto opUnary(string op)();
Unary plus/minus operators.

Example:
 auto l = 6 * metre;
 assert(+l == 6 * metre);
 assert(-l == (-6) * metre);

auto opUnary(string op)();
Prefix increment/decrement operators.

They are only provided dimensionless quantities, because they are semantically equivalent to adding the dimensionless quantity 1.

auto opBinary(string op, RhsV)(Quantity!(Unit,RhsV) rhs);
Quantity opOpAssign(string op, RhsV)(Quantity!(Unit,RhsV) rhs);
Addition/substraction of a quantity with the same unit.

Example:
 auto a = 3 * metre;
 auto b = 2 * metre;
 assert(a + b == 5 * metre);
 a -= b;
 assert(a == 1 * metre);

auto opBinary(string op, T)(T rhs);
auto opBinaryRight(string op, T)(T lhs);
Quantity opOpAssign(string op, T)(T rhs);
Multplication/division by a plain value (i.e. a dimensionless quantity not represented by a Quantity instance).

Example:
 auto l = 6 * metre;
 assert(l * 2 == 12 * metre);
 l /= 2;
 assert(l == 3 * metre);

auto opBinary(string op : "*", Rhs)(Rhs rhs);
auto opBinaryRight(string op : "*", Lhs)(Lhs lhs);
Multiplication with a unit instance.

Returns a quantity with the same value, but the new unit.

Example:
 auto l = 6 * metre;
 assert(l * metre == 6 * pow!2(metre));

auto opBinary(string op : "/", RhsU)(RhsU rhs);
auto opBinaryRight(string op : "/", Lhs)(Lhs rhs);
Division by a unit instance.

Returns a quantity with the same value, but the new unit.

Example:
 auto l = 6 * metre;
 assert(l / metre == 6 * dimensionless);

auto opBinary(string op : "*", RhsU, RhsV)(Quantity!(RhsU,RhsV) rhs);
Multiplication with another quantity.

Example:
 auto w = 3 * metre;
 auto h = 2 * metre;
 assert(w * h == 12 * pow!2(metre));

auto opBinary(string op : "/", RhsU, RhsV)(Quantity!(RhsU,RhsV) rhs);
Division by another quantity.

Example:
 auto s = 6 * metre;
 auto t = 2 * second;
 assert(s / t == 3 * metre / second);

int opEquals(RhsV)(Quantity!(Unit,RhsV) rhs);
auto opCmp(RhsV)(Quantity!(Unit,RhsV) rhs);
Comparison with another quantity of the same unit.

Example:
 auto a = 3 * metre;
 auto b = 4 * metre;
 auto c = 5 * second;
 assert(a != b);
 assert(a < b);
 assert(!__traits(compiles, a != c));
 assert(!__traits(compiles, a < c));

string toString(UnitString type = UnitString.name);
Returns a string representation of the quantity, consisting of the value and a unit symbol or name.

Example:
 auto l = 6 * metre / second;
 assert(l.toString() == "6 metre second^(-1)");
 assert(l.toString(UnitString.symbol) == "6 m s^(-1)");

auto pow(Exp, U)(U u);
auto pow(int numerator, uint denominator = 1u, U)(U u);
Raises a unit instance to a given power.

Because the exponent must be known at compile-time (as the type of the result depends on it), overloading the ^^ operator for units.

Example:
 enum squareMetre = pow!2(metre);

auto pow(Exp, Q : Quantity!(U,V), U, V)(Q q);
auto pow(int numerator, uint denominator = 1u, Q : Quantity!(U,V), U, V)(Q q);
Raises a quantity to a given power.

Because the exponent must be known at compile-time (to determine the unit of the result), overloading the ^^ operator for quantities is not possible.

Example:
 auto area = pow!2(5 * metre);

struct Conversion(T,alias R,alias I) if (isUnit!(T));
template Conversion(alias t,alias R,alias I) if (isUnitInstance!(t))
A conversion »link« to a target unit, consisting of a callable converting a value to the target, and one for converting it back.

Is used in the optional Conversions property of base units, see the documentation for UnitImpl.

auto convert(TargetUnit, Q : Quantity!(U,V), U, V)(Q q);
auto convert(alias targetUnit, Q : Quantity!(U,V), U, V)(Q q);
Converts a quantity to another unit.

The value type of the resulting quantity will be the same as the original one.

Examples:
 writeln(convert!gram(2 * kilogram));
 writeln(convert!kilogram(2000 * gram));
 writeln(convert!(milli(newton))(2 * newton));
 writeln(convert!(kilo(newton))(2000000 * gram * meter / pow!2(second)));
 writeln(convert!(micro(newton) / pow!2(milli(metre)))(1234.0 * pascal));

bool canConvert(TargetUnit, Q : Quantity!(U,V), U, V)(Q q);
bool canConvert(alias targetUnit, Q : Quantity!(U,V), U, V)(Q q);
Checks whether a quantity is convertible to a certain unit.

Examples:
 assert(canConvert!gram(2 * kilogram));
 assert(!canConvert!metre(2 * kilogram));

struct ScaledUnit(BaseUnit,alias toBaseFactor,string name,string symbol = null) if (isUnit!(BaseUnit));
template ScaledUnit(alias baseUnit,alias toBaseFactor,string name,string symbol = null) if (isUnitInstance!(baseUnit))
template scale(alias baseUnit,alias toBaseFactor,string name,string symbol = null) if (isUnitInstance!(baseUnit))
Shorthands for defining base units with a single conversion factor to another base unit.

The conversion is done by simply multiplying/dividing the value by the passed factor, which thus has to be defined for all value types this scaled unit is used with.

Note that a generic alias is accepted as scaling factor, which makes it possible to use runtime values as scale factors without writing a custom unit type.

Example:
 // The following three lines define the same unit. Most of the time, the
 // third syntax is the preferred one because it directly declares a unit
 // instance.
 alias ScaledUnit!(Metre, 0.0254, "inch", "in") Inch;
 alias ScaledUnit!(metre, 0.0254, "inch", "in") Inch;
 enum inch = scale!(metre, 0.0254, "inch", "in");

struct PrefixedUnit(BaseUnit,int exponent,alias System) if (isUnit!(BaseUnit) && !(isPrefixedUnit!(BaseUnit) && BaseUnit.prefixBase == System.base));
template PrefixedUnit(BaseUnit,int exponent,alias System) if (isPrefixedUnit!(BaseUnit) && BaseUnit.prefixBase == System.base)
template PrefixedUnit(alias baseUnit,int exponent,alias System) if (isUnitInstance!(baseUnit))
A unit with a scaling prefix applied, e.g. kilo(metre).

There is conceptually no difference between defining a regular conversion and prefixing a unit. However, PrefixedUnit automatically generates the name of the new unit, and it is able to fold multiple prefixes of the same system, e.g. milli(kilo(metre)) to just metre.

struct Prefix;
A named prefix, part of a PrefixSystem.

int exponent;
The power the prefix represents.

string name;
The name of the prefix, prepended to unit names.

string symbol;
The symbol of the prefix, prepended to unit symbols.

template PrefixSystem(long systemBase,alias getPrefixes) if (is(typeof(getPrefixes()) : Prefix[]))
A prefix system, used with PrefixedUnit.

Use the DefinePrefixSystem mixin to automatically generate a helper function like kilo() for every prefix in the system.

getPrefixes has to be a parameterless callable returning Prefix[]. This scheme is used (instead of directly passing the prefix array as a parameter) to reduce code bloat, because delegate literals are mangled much shorter than array literals. The effect on the binary size is quite strong because the mangled name of a PrefixSystem instance is part of every symbol in which a PrefixedUnit is involved.

Example:
 alias PrefixSystem!(10, { return [
     Prefix(-3, "milli", "m"),
     Prefix(3, "kilo", "k")
 ]; }) System;

template prefixTemplate(int exponent,alias System)
Shorthand for defining prefix templates like kilo!().

The created template, accessible via the result property, takes a unit instance and applies a prefix from the given list of prefixes to it.

Example:
 alias PrefixSystem!(10, { return [
     Prefix(-3, "milli", "m"),
     Prefix(3, "kilo", "k")
 ]; }) System;
 alias prefixTemplate!(-3, System) milli;
 alias prefixTemplate!(3, System) kilo;
 // Use the templates like this: milli!metre, kilo!metre, etc.

template DefinePrefixSystem(alias System)
Mixin template for creating prefix functions for all the prefixes in a prefix system. See PrefixedUnit and prefixTemplate.

Example:
 mixin DefinePrefixSystem!(PrefixSystem!(10, { return [
     Prefix(-3, "milli", "m"),
     Prefix(3, "kilo", "k")
 ]; });
 // Use milli!() and kilo!() as usual.

template Rational(int n,uint d = 1u)
A compile-time rational number.

If you explicitely specify the denominator, be sure to use an *unsigned* integer literal (e.g. 2u) – even though the template accepts only unsigned integers anyway, this seems to make a difference.

Note:
This was tailored to the specific needs of the units library, and isn't optimized at all.

template Sum(Lhs,Rhs) if (isRational!(Lhs) && isRational!(Rhs))
The sum of two compile-time rational numbers.

template Difference(Lhs,Rhs) if (isRational!(Lhs) && isRational!(Rhs))
The difference between two compile-time rational numbers.

template Product(Lhs,Rhs) if (isRational!(Lhs) && isRational!(Rhs))
The product of two compile-time rational numbers.