Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Units

Base Units
Scaled Base Units
Scaled Units

We define a unit as a set of base units each of which can be raised to an arbitrary rational exponent. Thus, the SI unit corresponding to the dimension of force is kg m s^-2, where kg, m, and s are base units. We use the notion of a unit system such as SI to specify the mapping from a dimension to a particular unit so that instead of specifying the base units explicitly, we can just ask for the representation of a dimension in a particular system.

Units are, like dimensions, purely compile-time variables with no associated value. Units obey the same algebra as dimensions do; the presence of the unit system serves to ensure that units having identical reduced dimension in different systems (like feet and meters) cannot be inadvertently mixed in computations.

There are two distinct types of systems that can be envisioned:

Units are implemented by the unit template class defined in boost/units/unit.hpp :

template<class Dim,class System> class unit;

In addition to supporting the compile-time dimensional analysis operations, the +, -, *, and / runtime operators are provided for unit variables. Because the dimension associated with powers and roots must be computed at compile-time, it is not possible to provide overloads for std::pow that function correctly for units. These operations are supported through free functions pow and root that are templated on integer and static_rational values and can take as an argument any type for which the utility classes power_typeof_helper and root_typeof_helper have been defined.

Base units are defined much like base dimensions.

template<class Derived, class Dimensions, long N> struct base_unit { ... };

Again negative ordinals are reserved.

As an example, in the following we will implement a subset of the SI unit system based on the fundamental dimensions given above, demonstrating all steps necessary for a completely functional system. First, we simply define a unit system that includes type definitions for commonly used units:

struct meter_base_unit : base_unit<meter_base_unit, length_dimension, 1> { };
struct kilogram_base_unit : base_unit<kilogram_base_unit, mass_dimension, 2> { };
struct second_base_unit : base_unit<second_base_unit, time_dimension, 3> { };

typedef make_system<
    meter_base_unit,
    kilogram_base_unit,
    second_base_unit>::type mks_system;

/// unit typedefs
typedef unit<dimensionless_type,mks_system>      dimensionless;

typedef unit<length_dimension,mks_system>        length;
typedef unit<mass_dimension,mks_system>          mass;
typedef unit<time_dimension,mks_system>          time;

typedef unit<area_dimension,mks_system>          area;
typedef unit<energy_dimension,mks_system>        energy;

The macro BOOST_UNITS_STATIC_CONSTANT is provided in boost/units/static_constant.hpp to facilitate ODR- and thread-safe constant definition in header files. We then define some constants for the supported units to simplify variable definitions:

/// unit constants 
BOOST_UNITS_STATIC_CONSTANT(meter,length);
BOOST_UNITS_STATIC_CONSTANT(meters,length);
BOOST_UNITS_STATIC_CONSTANT(kilogram,mass);
BOOST_UNITS_STATIC_CONSTANT(kilograms,mass);
BOOST_UNITS_STATIC_CONSTANT(second,time);
BOOST_UNITS_STATIC_CONSTANT(seconds,time);

BOOST_UNITS_STATIC_CONSTANT(square_meter,area);
BOOST_UNITS_STATIC_CONSTANT(square_meters,area);
BOOST_UNITS_STATIC_CONSTANT(joule,energy);
BOOST_UNITS_STATIC_CONSTANT(joules,energy);

If support for textual output of units is desired, we can also specialize the base_unit_info class for each fundamental dimension tag:

template<> struct base_unit_info<test::meter_base_unit>
{
    static std::string name()               { return "meter"; }
    static std::string symbol()             { return "m"; }
};

and similarly for kilogram_base_unit and second_base_unit. A future version of the library will provide a more flexible system allowing for internationalization through a facet/locale-type mechanism. The name() and symbol() methods of base_unit_info provide full and short names for the base unit. With these definitions, we have the rudimentary beginnings of our unit system, which can be used to determine reduced dimensions for arbitrary unit calculations.

Now, it is also possible to define a base unit as being a multiple of another base unit. For example, the way that kilogram_base_unit is actually defined by the library is along the following lines

struct gram_base_unit : boost::units::base_unit<gram_base_unit, mass_dimension, 1> {};
typedef scaled_base_unit<gram_base_unit, scale<10, static_rational<3> > > kilogram_base_unit;

This basically defines a kilogram as being 10^3 times a gram.

There are several advantages to this approach.

  • It reflects the real meaning of these units better than treating them as independent units.
  • If a conversion is defined between grams or kilograms and some other units, it will automatically work for both kilograms and grams, with only one specialization.
  • Similarly, if the symbol for grams is defined as "g", then the symbol for kilograms will be "kg" without any extra effort.

We can also scale a unit as a whole, rather than scaling the individual base units which comprise it. For this purpose, we use the metafunction make_scaled_unit. The main motivation for this feature is the metric prefixes defined in boost/units/systems/si/prefixes.hpp.

A simple example of its usage would be.

typedef make_scaled_unit<si::time, scale<10, static_rational<-9> > >::type nanosecond;

nanosecond is a specialization of unit, and can be used in a quantity normally.

quantity<nanosecond> t(1.0 * si::seconds);
std::cout << t << std::endl;    // prints 1e9 ns

PrevUpHomeNext