A library to work with quantities and units

Package Information

Version 0.5.1 (2016-Dec-10)
License BSL-1.0
Copyright Copyright © 2013-2015, Nicolas Sicard
Authors Nicolas Sicard
Registered by Nicolas Sicard
Dependencies none





About quantities

Build Status

The purpose of this small library is to perform automatic compile-time or runtime dimensional checking when dealing with quantities and units.

There is no actual distinction between units and quantities, so there are no distinct quantity and unit types. All operations are actually done on quantities. For example, meter is both the unit meter and the quantity 1m. New quantities can be derived from other ones using operators or dedicated functions.

Quantities can be parsed from strings at runtime and compile-time.

The main SI units and prefixes are predefined. Units with other dimensions can be defined by the user.

Copyright 2013-2015, Nicolas Sicard.

License: Boost License 1.0.

Design rationale (work in progress)

  1. The library defines a Quantity type (a template) that represents a physical quantity, or any user-defined type of quantity. A quantity can be seen as the product of a scalar value and a vector of dimensions.

  2. A Quantity is a wrapper struct around a numeric value, where the only payload is this numeric value; no other data is stored. So the memory size of a quantity is the same as its underlying numeric type. With optimizations on, the compiler generates the same code as if normal numeric values were used.

For the moment, only built-in numeric types are handled. But it should be possible to make it work with any "number-like" type.

  1. Two quantities with the same dimensions share the same type (assuming the underlying numeric types are the same). Thus functions and types using quantities generally won't have to be templated if the dimensions of the quantities are known at compile time.

Impact on the design

The main consequence of principle #3 is that all quantities sharing the same dimensions are internally expressed in the same unit, which is the base unit for this quantity. For instance, all lengths are stored as meters, which is the base unit of length. The quantity 3km is stored as 3000m, 2min is stored as 120s, etc. The drawback (possibly an important one) is that, when assigning a new value to a quantity, the binary representation is preserved only if the quantity is expressed in the base unit.

An indirect consequence is that there is no unit symbol associated with a quantity. The only relevant symbol would have been the one of the base unit, but it's rarely the best choice. But in practice, when formatting a quantity, the unit is usually chosen in advance. If not, no simple algorithm is capable of guessing the relevant unit. So I have decided that a quantity wouldn't format itself correctly. Instead, for now, the toString function prints the value and the dimensions vector. To print the units properly, the user can use the provided format helpers, or use the result of the value method.


Working with predefined units

The package defines all the main SI units and prefixes, as well as aliases for their types.

import quantities;
import std.math : approxEqual;
import std.stdio : writeln, writefln;

void main()
    auto distance = 384_400 * kilo(meter);
    auto speed = 299_792_458  * meter/second;
    Time time;
    time = distance / speed;
    writefln("Travel time of light from the moon: %s s", time.value(second));
Dimensional correctness is checked at compile-time
Mass mass;
static assert(!__traits(compiles, mass = 15 * meter));
static assert(!__traits(compiles, mass = 1.2));
Calculations can be done at compile-time
enum distance = 384_400 * kilo(meter);
enum speed = 299_792_458  * meter/second;
enum time = distance / speed;
Create a new unit from the predefined ones
enum inch = 2.54 * centi(meter);
enum mile = 1609 * meter;
writefln("There are %s inches in a mile", mile.value(inch));
Create a new unit with new dimensions
// Create a new base unit of currency
enum euro = unit!(double, "C"); // C is the chosen dimension symol (for currency...)

auto dollar = euro / 1.35;
auto price = 2000 * dollar;
writefln("This computer costs €%.2f", price.value(euro));

At compile time:

enum distance = si!"384_400 km";
enum speed = si!"299_792_458 m/s";
enum time = distance / speed;

static assert(is(typeof(distance) == Length));
static assert(is(typeof(speed) == Speed));

At runtime:

auto data = [
    "distance-to-the-moon": "384_400 km",
    "speed-of-light": "299_792_458 m/s"
auto distance = si!Length(data["distance-to-the-moon"]);
auto speed = si!Speed(data["speed-of-light"]);
auto time = distance / speed;
Example: chemistry session
// Use the predefined quantity types (in module
Volume volume;
Concentration concentration;
Mass mass;

// Define a new quantity type
alias MolarMass = typeof(kilogram/mole);

// I have to make a new solution at the concentration of 25 mmol/L
concentration = 25 * milli(mole)/liter;

// The final volume is 100 ml.
volume = 100 * milli(liter);

// The molar mass of my compound is 118.9 g/mol
MolarMass mm = 118.9 * gram/mole;

// What mass should I weigh?
mass = concentration * volume * mm;
writefln("Weigh %s of substance", mass); 
// prints: Weigh 0.00029725 [M] of substance
// Wait! That's not really useful!
// My scales graduations are in 1/10 milligrams!
writefln("Weigh %.1f mg of substance", mass.value(milli(gram)));
// prints: Weigh 297.3 mg of substance

Available versions

0.5.1 0.5.0 0.4.5 0.4.4 0.4.3 0.4.2 0.4.1 0.4.0 0.3.1 0.3.0 0.2.1 0.2.0 0.1.0 ~master