The Quantity Library

Copyright © 2001 by Michael S. Kenniston. For the most recent version check www.xnet.com/~msk/quantity. Permission is granted to use this document without restriction so long as this copyright notice appears in it.

Introduction

When performing scientific or engineering calculations, one of the ways in which people have traditionally checked their work is to keep track of the dimensions of all intermediate results, and then verify that the answer has the proper dimensions. For example, if you are expecting a velocity but your answer comes out as a density, you know you've made a mistake somewhere. Unfortunately, few if any programming languages support this concept, so programmers almost always use raw numeric types (e.g. "double") to represent all the different quantities in a program.

The Quantity Library extends the C++ language to associate dimensions with each quantity variable or value, and it arranges for these dimensions to be checked for consistency at compile-time. There is no need for additional run-time space or time overhead, so ideally the final program should execute as fast as an equivalent program with no such checking.

To use the library, simply:

#include "cuj/quantity/quantity.hpp"
If you want to do output, you must also:
#include "cuj/quantity/quantity_io.hpp"
To use non-SI units and/or predefined physical constants:
#include "cuj/quantity/other_units.hpp"
#include "cuj/quantity/physical_constants.hpp"

Dimensions

NIST SP811 describes the seven fundamental dimensions of the physical universe:

These seven dimensions provide the foundation upon which the SI system of measurements is built. Every other measurement unit is derived from these seven, so the dimensionality of a quantity is simply a 7-tuple of integers in which the first integer is the exponent of length, the second is the exponent of mass, and so on down the list. For example, a simple length has dimensions (1,0,0,0,0,0,0), a volume has dimensions (3,0,0,0,0,0,0), and a speed has dimensions (1,0,-1,0,0,0,0).

The library defines a template class that is used to specify these dimensions in the obvious way, so for example it includes the definition:

typedef dimensions< -3, 1, 0, 0, 0, 0, 0 > mass_density_d;
(mass density is mass per volume). There is a long list of predefined dimensionalities, but if you need one that isn't there you can define your own in any application program.

Quantities

Quantities, as defined in SP811, are simply values that include dimensions. For example, "3" is a number but "3 meters" is a quantity. The library allows you to declare variables that can hold quantities, but you must specify (at compile-time) the dimensions of the quantity. For example, a user program can declare:

quantity< mass_density_d > chamber_density;
and then the compiler will verify that only values with compatible dimensions are ever assigned to that variable. All quantities have a default constructor, so you can create arrays or vectors of them. This default constructor leaves the value undefined, just as it would for a number.

There is a special case where all the dimensions are zero. The meaning is that such a value is just a pure number, so the library handles this case by using the primitive numeric type (e.g. "double") to hold such a value. (Instantiating the dimensions<> template class with all zeroes is prohibited.)

Base Units

Once you have declared a quantity variable, you want to assign values to it. These values must be constructed with the seven fundamental SI units:

or with units derived from those seven like watt, newton, and pascal. For example, you can say:
chamber_density = 3.458 * kilogram() / ( meter() * meter() * meter() );
The units here are absolutely required; if you try to simply assign the number 3.458 the compiler will flag it as a fatal error. Unfortunately it will often produce the sort of verbose and obscure message typical of C++ template errors, but it will at least point you at the incorrect line. (The empty parentheses "()" are an artifact of C++; all predefined units and quantities are defined as inline functions to help the optimizer generate better object code.) There are square() and cube() functions defined on quantities, so the above can be stated somewhat more concisely as:
chamber_density = 3.458 * kilogram() / cube( meter() );

Defining every SI unit with every possible decimal prefix would lead to an explosion of identifiers, so only the primary units are defined (except that both "kilogram" and "gram" are defined). All the SI prefixes such as milli and mega are defined as pure numbers which may be used as multipliers:

quantity< energy_d > max_energy = 34 * mega() * joule();

Non-Base Units

The SI system includes "derived units" such as newton and watt. These are also defined by the library and can be used just like any of the base units. In addition, there is a separate header file called "other_units.hpp" which contains a long list of units which are not approved for use with SI (like foot, slug, and btu), but which you may need in your program to deal with legacy data or special requirements.

There is also a static member function zero() defined for the quantity of each dimensionality. This is provided for convenience, for example so you can compare a quantity to zero. Each different dimension has its own zero, because 0 * meter() is not the same as, and cannot be compared to, 0 * kilogram(). In fact:

if( current_power > 0 )
is illegal; you must say:
if( current_power > quantity< power_d >::zero() )
or
if( current_power > 0 * watt() )

Operations and Expressions

The library defines the usual arithmetic operators +, -, *, and / on all legal combinations of quantities and numbers. Power and root functions are also defined. Specifically:

The dimensions of the result of every operation are computed automatically by the library. The only time the user ever has to specify dimensions is when declaring a variable. The nth_power() and nth_root() functions are templated, because the compiler has to know which power or root you want in order to determine the dimensions of the result. For example:

quantity< volume_d > cube_volume;
cube_volume = 3.456e+2 * cube( meter() );
quantity< length_d > cube_side;
cube_side = nth_root< 3 >( cube_volume );
cout << "the side is " << cube_side << "\n";
or, more concisely:
cout << "the side is " << nth_root< 3 >( 3.456e+2 * cube( meter() ) ) << "\n";

Exponential and trig functions are not defined on quantities.

The following table summarizes the legal and illegal operations.

OperationOperand Type(s)Result Type
Constructors quan()
quan( quan )
quan
quan( num )
num( quan )
quan1( quan2 )
illegal
Assignment quan = quanquan &
quan = num
num = quan
quan1 = quan2
illegal
Addition
Subtraction
quan += quan
quan -= quan
quan &
quan += num
quan -= num
num += quan
num -= quan
quan1 += quan2
quan1 -= quan2
illegal
+ quan
- quan
quan + quan
quan - quan
quan
quan + num
quan - num
num + quan
num - quan
quan1 + quan2
quan1 - quan2
illegal
Multiplication quan *= numquan &
num *= quan
quan *= quan
quan1 *= quan2
illegal
quan * num
num * quan
quan
quan1 * quan1quan2
quan1 * quan2If quan1 and quan2 are reciprocals: num
Otherwise: quan3
Division quan /= numquan &
num /= quan
quan /= quan
quan1 /= quan2
illegal
quan / numquan
num / quan1quan2
quan / quannum
quan1 / quan2quan3
Powers nth_power< int >( quan1 )
square( quan1 )
cube( quan1 )
If int == 0: num
If int == 1: quan1
Otherwise: quan2
Roots nth_root< int >( quan1 )
sqrt( quan1 )
When dimensions of quan1 are all even multiples of int: quan2
Otherwise: illegal
Transcendental
functions
log( quan ), log10( quan ), etc.
exp( quan ), pow( quan, num ), etc.
sin( quan), cos( quan ), tan( quan ), etc.
illegal

Input, Output, and Unit Conversion

All I/O functions are defined in a separate header file called "quantity_io.hpp" so that you don't have to pull in <iostream> unless you really need to.

There is currently no input operator>> defined. To read a quantity, you must read the number and then multiply by the units, e.g.

double d;
cin >> d;
quantity< power_d > bulb_power = d * watt();
You can't forget to multiply by the units, because if you do the dimensions won't match and you'll get a compile-time error.

There is an output operator<< defined, but it always formats quantities using just the seven base units. The output format is as close as you can get in plain ascii to the official notation described in NIST SP811, e.g.:

cout << 456789  * kilo() * watt() * hour() << "\n";
produces:
1.64444e+012 m+2 kg s-2
(the official notation would be "1.644 44 · 1012 · m2 · kg · s-2 ").

If you want to force output in particular units, simply divide by that unit (and print the unit yourself). For example,

cout << "One parsec is " << parsec() / mile() << " miles long.\n";
or
quantity< energy_d > todays_consumption;
//...
const quantity< energy_d > kWhr( kilo() * watt() * hour() );
cout << "todays energy consumption was " << todays_consumption / kWhr << " kWhr \n";

Representation

Internally, each quantity is represented at run-time as one "double". If you wish to use "float" or "long double" instead, you can do so by defining CUJ_QUANTITY_REP_TYPE:

#define CUJ_QUANTITY_REP_TYPE long double
#include "cuj/quantity/quantity.hpp"
IMPORTANT: You must ensure that the representation is consistent across all compilation units of your program. This is a simple but not very elegant implementation, and incorrect usage will not be detected by the compiler.

Efficiency

In debug builds, calculations with quantities will typically run much more slowly than calculations with raw numbers. However, once you turn on the optimizer, the performance should be as good, or nearly as good, as that of raw numbers. Of course, this depends on the optimizer, and some do a measurably better job than others.

Portability

A primary design goal of this library was to run on as many compilers as possible, in spite of the fact that some of those compilers are not ISO-compliant and don't even implement the entire C++ language. This led to some peculiar implementation methods hidden inside the library, and but it had little effect on the interface so users should not notice anything out of the ordinary.

Gotchas

Non-features

There are lots of things a units library could do that this one does not:

Of course, any of these things may be added to the library later, but in the interest of getting a simple, solid, portable library working, all the fancy stuff was left for possible future extensions.

Furthermore, if you need a more sophisticated library than this one, the "SIunits" library from Fermilab may suit your needs. It has more features than the Quantity Library, but it is not as portable; in particular it does not support MSVC. See http://www.fnal.gov/ for details.

References

NIST SP811 is the National Institute of Standards and Technology Special Publication 811. This, along with a great deal of other useful information about measurements and units, is available at their web site: http://www.nist.gov/.


Last updated: 2001-07-13