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:
- Integration with the rest of Phobos (std.datetime, std.math, …)
- Replace the proof-of-concept unit conversion implementation with an optimized one – currently some unneeded function calls are generated.
- For scale/ScaledUnit, use runtime rational/two longs instead of double conversion per default, to avoid precision issues?
- Benchmark quantity operations vs. plain value type operations.
- Make quantities of the same unit implicitly convertible if the value types are – e.g. via ImplicitConversionTargets once multiple alias this statements are allowed.
- Are multiple conversion targets for a unit really needed? Disallowing that would remove some odd corner cases.
- Just forward Quantity instanciations with unit dimensionless to the value type altogether to avoid the current limitations regarding alias this?
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;
- auto opBinary(string op : "*", Rhs)(Rhs rhs);
- enum UnitString;
- Possible string representations of units.
- 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)");
- this(OtherV)(Quantity!(Unit,OtherV) other);
- 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.
- 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.