Quantities
package from a user's perspective. It explains the main ideas behind the software and defines terms which are used later. It discusses the object types provided and describes the operations which can be performed on and with Quantities
objects during their life cycle. Finally, it is shown how a user can expand the library by introducing specific modifications to already present quantities or by adding completely new types.Quantities
package is that of a quantity. A quantity is an entity which is composed of a (numerical) value and a unit, and related to other quantities by a dimension. Quantities are used to describe some "attribute of a phenomenon, body or substance" [1] in a quantitative way. For example, time is a particular quantity. If we perform calculations with quantities, we are engaged in quantity calculus.The quantity's unit is the standard of measurement of that quantity. For example, time is measured in seconds (s). Although the SI (Système International d'Unités) provides only a single unit for a particular quantity, quite often several units for this quantity are in practical use. For example, time is also measured in minutes (min). Some units [e.g., millisecond (ms)] contain a prefix, which defines multiples of 10 of the base unit. In quantity calculus, the quantity represents the product of the value and the attached unit. The value of a quantity can be recalculated to be measured in any of its units. This will be called "to recalculate the quantity from one of the units into another unit". As an additional feature, we will provide units with a name and a symbol for easy identification.
Each quantity is related to one or more of seven base quantities defined by the SI (Système International d'Unités). These base quantities are length, mass, time, electric current, thermodynamic temperature, amount of substance and luminous intensity. The dimension of a quantity is given by the powers to which these base quantities are raised in the relation. We will call these seven numbers the components of a quantity's dimension.
The dimension of a quantity is important in quantity calculus to define commensurability. Two quantities are commensurate if their dimensions (i.e., the seven powers as defined above) are identical. Only commensurate quantities can be assigned to each other in a calculation. For example, a product of two lengths has the dimension of an area and can be assigned to an area quantity. However, it can not be assigned to a time quantity. For non-commensurate quantities also some other operations are impossible (see below). If all seven powers are zero, we call the quantity dimensionless.
The Quantities
package provides header files and libraries which facilitate to write C++ programs incorporating quantity calculus. This is basically done by defining various types which have built-in abilities to follow and enforce the rules of quantity calculus. Thus, commensurability is tested for numerical and assignment operations, and units are taken into account. Furthermore, other operations are provided (Quantity
library). There is also a collection of often used scientific (physical) quantities (see, Physical Quantities) in the PhysicalQuantities
library, which is part of Quantities
.
Once a C++ program has declared objects with the types mentioned, they can be used to perform quantity calculus. Such an object is generally referred to as a quantity object in the following. The type of such an object is the quantity type. We will further categorize quantity objects by their mode, i.e. whether they refer to a variable, a constant, a unique constant, or an unnamed transient variable.
A variable is a quantity object with a variable value. Consequently, it can be constructed with some value, be assigned to, and the value can be changed during an operation on the variable object.
A constant is a quantity object with a value which is fixed at construction. It can neither be assigned to nor have its value changed during an operation. However, it can be used in operations which leave the value of the constant object untouched, and the result of that operation can be assigned to a variable object.
A unique constant is a quantity object which does exist only once in a program. Its value is fixed already at compile time, and there is no way to construct another instance of such a unique constant object from user code. A change of the value is not possible. However, similar to a constant object it can be used in operations which leave the unique constant object untouched.
Unnamed transient quantity objects are often internally created (generated quantity objects), for example as return values of a function. Such objects can be used in operations, but in lieu of a name within the program it is not possible to assign or change the value. Other sources of such quantity objects are certain constructor calls. If this is the constructor of a well-defined quantity, the resulting object carries full unit information. However, generated quantity objects have only limited knowledge about the unit in which the value is stored. In particular, it is assumed that the standardization factor of the unit is 1.0. Thus, generated quantity objects should be used with great care (see below).
In rare cases, dynamic quantity objects are generated and returned from a function. Such an object has a type Dynamic<ST>, where ST is the storage type used (see, Object Types in Quantities (to be finalized)). A dynamic quantity object's properties can not be tested a compile time, since they are only defined at run time. Thus, they are not subject to the usual rules within this package, and could result in errors, which are only detected when the program runs. Consequently, such objects should be avoided as far as possible. At least, they should be converted into or assigned to normal quantity objects as soon as possible after they are returned from a function.
QuantityCluster objects are aggregates of simple quantity objects. In particular, the library implements variable vector, variable tuple, and variable vector tuple objects.
A variable vector object aggregates several variable objects of the same type (homogeneous aggregate). It is intended to avoid some overhead of collecting the various objects in a vector of variable objects.
A variable tuple object aggregates several variable objects of possibly different types (heterogeneous aggregate). It provides an ordered collection of these objects.
A variable vector tuple object aggregates several variable vectors in a certain order.
Quantities
package are discussed here only briefly from a technical point of view. A more detailed description can be found elsewhere for Quantity and PhysicalQuantities types.Dimensions
Units - NonPrefixable, Prexiable, Prefixed, Compound, Composed
Quantities
The numerical value of a quantity object is stored internally in a certain appropriate type, the "storage type". The default storage type is double
. In the physical quantities (Physical Quantities) provided, the storage type is mentioned in the type name. For example, IntTime
is a type for physical quantity time with storage type int
. Physical quantities that use the default storage type do usually not have such a prefix, i.e. Time
is assumed to have double
as the storage type.
A quantity stores its value in a particular Unit, the "storage unit". This Unit's type is incorporated into the Quantity's type. Various types of a Quantity with different storage units are derived from the same parent quantity.
Moreover, all Quantity types derived from a particular parent quantity are characterized by a "standard unit". Each Quantity provides methods to recalculate its value into the standard unit, and from the standard unit into the storage unit. This unit is used as a standard for the recalculation between different quantities derived from the same parent quantity.
Finally, each parent quantity defines a "default unit". This default unit is used as the storage unit, if the latter is not provided when the Quantity is defined (as a template, see below).
Several Variable, Constant, and UniqueConstant types may be derived from a common parent quantity class. For example, Time
, MinuteTime
, and TimeConstant
have the same parent quantity. As discussed below, objects of these related types can simply be converted by copy construction.
Technically most of the quantity types are typedefs of complex templates. Furthermore, the special case of a UniqueConabstract stant is an implementation of a Singleton. According to convention, UniqueConstants are written in capital letters like macro constants.
The fact that many operations done with Quantities are implemented as compile-time constructs (i.e., with template formulations), offers the possibility to use the compiler to check and help eliminate bugs in the code, for example if quantities are incommensurate. In this regard, `your compiler is your friend' [2] by issuing error or warning messages whenever illegal constructs are encountered.
PhysicalQuantities
... (to be written) ...
Quantities
package is installed according to the instructions given elsewhere.
In order to use the classes within the Quantities
package in your program, you have to (a) include header files in your client code, and (b) link the program against libraries.
For the discussion of header files, we assume that you point your compiler to use the installation directory of Quantities
in the search path for header files. For example, with GNU gcc use
-I path-to-your-install-directory/Quantities
It is usually best to include header file Quantities.h
as
#include "Quantities.h"
This directive includes all header files from the Quantities
package.
If you want to include only some of the header files, use
#include "Quantity/Prefix.h"
#include "Quantity/Unit.h"
#include "Quantity/Dimension.h"
#include "Quantity/Quantity.h"
#include "Quantity/Generic.h"
#include "Quantity/Variable.h"
#include "Quantity/Constant.h"
#include "Quantity/UniqueConstant.h"
#include "PhysicalQuantities/PhysicalQuantities.h"
to include header file Prefix.h
, Unit
,h, Dimension.h
, Quantity.h
, Generic.h
, Variable.h
, Constant.h
, UniqueConstant.h
, and PhysicalQuantities.h
respectively. This is only recommended for experienced users, and may be important if you want to override some of the definitions in the header files. Also, in the same way, only specific physical quantity header files, e.g. Time.h
, can be included, by using e.g.
#include "PhysicalQuantities/Time.h"
If you do not want to explicitly qualify the namespace for names from the Quantities
package, you may include a using
directive into your code:
If you use special constructs (see, Functions and Operations not for Direct Use) from this library, you might have to include additionalusing namespace quantity
using
directives to your code: Declarations and definitions for PhysicalQuantities are given in separate namespaces. The name of these namespaces isusing namespace BSUtilities using namespace unit using namespace dimension
xxx
for PhysicalQuantity Xxx
, respectively. The namespace contains the respective Dimension, Units, and Quantity definitions. Of these, user code in most cases will only need the names of the Unit classes. If you do not give a directive (withusing namespace xxx
xxx
substituted by the name of the PhysicalQuantity) you have to qualify the scope if you use a unit, e.g.: In the following examples, we will omit these qualifications.Length (1.0, length::Minute);
As special case is namespace time
. It may conflict with a name in time.h
. Thus, to disambiguate, the scope qualifier quantity::time mazy be needed.
The linker must be instructed to use various libraries in the Quantities
package when linking the client program. For GNU gcc, use
Also, you must link your program against the-Lpath-to-your-install-directory/lib -lPhysicalQuantities -lQuantity
BSUtilities
library: -Lpath-to-your-BSUtilities-install-directory/lib -lBSUtilities
which according to C++ rules disappeares when program flow leaves the block of code in which is it created, or on the stack (with operatorTime t; // example: default construction, 0 s
new
), e.g. In the latter case, the object is persistent. However, the client code has to make sure to explicitlyTime *t = new Time; // also calls default constructor
delete
the object later when it is no longer needed, in order to avoid memory leaks (see Destruction of Quantity Objects).
Of course, static
quantity objects are also possible (except unique constant objects).
A quantity object (except UniqueConstant objects) can be constructed in several ways:
Time
is a time quantity with storage type double and storage unit seconds, the following declaration constructs a variable quantity object with the name t and the value 0.0 s: Time t;
This constructs an object with the value given as argument, which is assumed to be in the storage unit of the quantity.Time t(5.0); // t = 5 s
This statement constructs a Time object which has storage type double from the int value given as argument. The int value is converted into double.Time t(5); // t = 5 s
Such constructs should be avoided.Time t(`a'); // t = 97 s
Here, we assume that IntTime is a type for times given as integer number, i.e. the storage type is int.IntTime t(5.9); // issues at least a warning with gcc
This constructs an object with the value given as argument, which is assumed to be in the unit given as the second argument. In contrast to the previous constructor call, this makes the storage unit explicit.Time t(5.0, Second ()); // t = 5 s, as above
This constructs an object with the value given as argument, which is assumed to be in the unit given as the second argument and is recalculated into the storager unit. In this construct, the type of the second argument is tested at compile time to make sure that the unit given is a valid unit for the parent quantity. Thus,Time t(5.0, Minute ()); // t = 5 min = 300 s
is invalid code and should be rejected by the compiler. Note, that throughout this document, we denote invalid code by "ERROR".Time t(5.0, Metre ()); // ERROR
This constructs an object with the value given as argument, which is assumed to be in the unit given as the unit symbol in the second argument. Again, recalculation occurs. In this construct, the type of the second argument can not be tested at compile time. However, at run time, the character constant provided is tested versus the symbols of all available units for the parent quantity. If it is not found in the list of these unit symbols, aTime t(5.0, "min"); // t = 5 min = 300 s, as above
UnitMismatch
exception (Exceptions) is thrown. Thus, would result in such an exception.Time t(5.0, "m"); // exception at run time
This constructor works simular to the previous one with a value and a character constant as argument.std::string str ("min"); // define string Time t(5.0, str); // t = 5 min = 300 s, as above
Time t (1.0); // construction of source Time t1 (t); // copy construction of target, t1 has // properties identical to t
This copy constructor acts as a conversion between quantity objects of different type, as long as they are related by the same parent quantity.MinuteTime t (1.0, Minute ()); // construction, 1 min Time t1 (t); // copy construction, t1 stores 60 s
Here,Time t (1.9); // construction int exp = 2; // exponent for power function SquareTime tsq (pow(t, exp)); // calculate t^2, use dynamic // return object for copy // construction Time t1 (pow (t, exp)); // DimensionMismatch exception SquareMinuteTime tm1 (pow (t, exp)); // calculates t^2, stores in // unit min^2 after // recalculation
SquareTime
and SquareMinuteTime
are quantity types to store a squared time quantity in s^2 or min^2, respectively.In the last line of this example the expression in parentheses returns an unnamed transient quantity object. Commensurability in this case is tested by comparing the dimensions of the generated quantity and the target quantity. As long as the two quantities are commensurate, the client code does not need to care about the resulting conversions.Time t (1.0); // construction Time t1 (2.0); // construction Time t2 (3.0); // construction Time t3 (t * t1 / t2); // copy construction, t3 stores 2/3 s
Quantities
package, and yields a link time error: In special cases, however, some quantity types may define the corresponding conversion. For example, conversion between different types of temperature (ThermodynamicTemperature, CelsiusTemperature, FarenheitTemperature) are allowed:Time t (1.0); // construction Length l (t); // ERROR
ThermodynamicTemperature T (298.0);// construction, 298 K CelsiusTemperature T1 (T); // copy construction, 25 oC
For objectTime t (1.); // construction of a variable quantity object TimeConstant t1 (t); // t1 is a constant quantity object with // value 1 s.
t1
now all restrictions of a constant quantity object apply. Since the above code generates a (superfluous) temporary object, and destroys it immediately after copying toTime t = Time (5.); // copy construction by unnamed object // t now contains now 5 s Time t1 (5.); // t1 = 5 s
t
(see also [3]), it should possibly be avoided, and direct construction, as for t1
above should be preferred.operator<<
see Input and Output Operations with Quantity Objects): std::cout << Time (5.) << std::endl; // prints "5 s"
Variable and constant quantity objects can be used after construction in the C++ client code just as built-in types.
Unique constant objects do not need to be constructed explicitly. They are generated at first use. Consequently, you can just write the name of a unique constant in an expression (for assignment in the second line of code in this example, see section Assignment Operations with Quantity Objects):
These two lines first construct an AmountOfSubstance object m, with contents 1 mol. Then, m is multiplied by the Faraday constant to yield an electric charge, in the present case this should be 96487 C.AmountOfSubstance m(1., Mole ()); // construction, 1 mol ElectricCharge Q = m * FARADAYCONSTANT; // multiply by F = 96487 C/mol // and get an ElectricCharge
For example, you can assign time quantities to each other, but not a length to a time:
In particular, assignment from an unnamed transient quantity object is possible if the source and the target quantity object are commensurate:Time t1 (1.0); // construction, value 1 s Time t2; // default construction, value 0 s t2 = t1; // assignment, now t2 has value 1 s MinuteTime t3 (1.0); // construction, value 1 min t2 = t3; // assignment, now t2 has value 60 s Length l (5.0) // construction, value 5 m t2 = l; // ERROR, incommensurate quantities
Assignment to a constant quantity object is only possible within a statement likeTime t1 (1.0); // construction, value 1 s Time t2; // default construction, value 0 s t2 = (t1 * t1)/t1; // value of t2 now 1 s
Such a statement can be regarded as initialization of the constant quantity object, and symbol = is not an assignment (see [3], and the precautions mentioned there).Time t (5.9); // construction of variable time, 5.9 s ConstantTime tc = t; // copy initialization
A std::string can also be assigned to a variable. This is described below (see Input Operations with Quantity Objects).Time t1 (25.); // construction, value 25 s Time t2, t3; // default construction, both objects have value 0 s t3 = t2 = t1; // now all three objects have value 25 s
Quantities
package provides mathematical operations with and between quantity objects. Some of these operations concern only a single quantity object and calculate a result from it. Some other operations concern two quantity objects and the result depends on both of them. Note that bitwise operations are not provided for quantity objects. A complete list of these functions is given below (it is assumed in the examples that object t1 is dimensionless, and has a storage type double):Time t (1.0); // constructor, 1 s Frequency omega (10.0); // constructor, 10 Hz double t_result = log10 (omega * t); // for multiplication, // see below, // t_result: 1.0
operation | example function call | comments |
exp |
| calculate the exponential of the quantity object value |
log |
| calculate the natural logarithm of the quantity object value |
log10 |
| calculate the logarithm (base 10) of the quantity object value |
sin |
| calculate the sine of the quantity object value it is assumed that the value is in rad |
cos |
| calculate the cosine of the quantity object value it is assumed that the value is in rad |
tan |
| calculate the tangent of the quantity object value it is assumed that the value is in rad |
sinh |
| calculate the hyperbolic sine of the quantity object value it is assumed that the value is in rad |
cosh |
| calculate the hyperbolic cosine of the quantity object value it is assumed that the value is in rad |
tanh |
| calculate the hyperbolic tangent of the quantity object value it is assumed that the value is in rad |
asin |
| calculate the arc sine of the quantity object value it is assumed that the value is in rad |
acos |
| calculate the arc cosine of the quantity object value it is assumed that the value is in rad |
atan |
| calculate the arc tangent of the quantity object value it is assumed that the value is in rad; for the alternative atan2 function, see below. |
operation | example function call | comments |
unary+ |
| return the object itself the return quantity type is identical to that of the original object |
unary- |
| return an object with the negative value of the argument; the return quantity type is identical to that of the original object |
operator* |
| multiply from the right; The object to the right of the operator can not only be of the storage type. This is no problem, if the type of this object is such that it can safely be converted or promoted into the storage type of the quantity object. Thus, the above statement should give the desired result, since the int value can readily be converted into the default storage type double , and the result be used for the multiplication. However, if precision is lost during such a conversion, care should be taken. For example, In some cases, the compilation process will issue a warning or even an error with such a construct. As with construction of quantity objects (see Construction of Quantity Objects), due to the promotion/conversion properties of C++, possibly non-meaningful constructs can be written, e.g.
In any case, the return quantity type is identical to that of the original object, except for the fact that a variable object is returned even if a constant or unique constant object is multiplied. |
operator* |
| multiply from the left; Similar comments as regards the storage type as given above for the multiplication from the right apply here. The return quantity type is identical to that of the original object, except for the fact that a variable object is returned even if a constant or unique constant object is multiplied. |
operator*= |
| multiply by an object of storage type from the right and assign to the object itself; the object must be of variable mode; Similar comments as regards the storage type as given above for the multiplication from the right apply here. the return quantity type is identical to that of the original object; no new object is generated |
operator/ |
| divide by an object of storage type on the right and assign to the object itself; the object must be of variable mode; Similar comments as regards the storage type as given above for multiplication apply here. the return quantity type is identical to that of the original object; no new object is generated |
operator/ |
| divide an object of storage type by quantity object; the return quantity type differs from that of the original object; the numerical result is calculated from the value in the standard unit |
operator/= |
| divide by an object of storage type and assign to the object itself; Similar comments as regards the storage type as given above for multiplication apply here. the return quantity type is identical to that of the original object; no new object is generated |
sqrt |
| calculate the square root; the return quantity type differs from that of the original object; the numerical result is calculated from the value in the standard unit; first alternative |
sqrt |
| calculate the square root; the return quantity type differs from that of the original object; the numerical result is calculated from the value in the standard unit; second alternative |
abs |
| calculate the absolute value; the return quantity type is a variable with identical dimension and unit as the argument; the old C style fabs() function is not provided, since abs() is overloaded for various data types in C++ |
ceil |
| calculate the smallest integer value not less than the argument; the return quantity type is a variable with identical dimension and unit as the argument; |
floor |
| calculate the largest integer value not greater than the argument; the return quantity type is a variable with identical dimension and unit as the argument; |
modf |
| decompose the first argument into integer and fraction parts; the return quantity type corresponds to that of the first argument; it is always of mode variable, irrespective of the mode of the first argument; this quantity object contains the fraction part; the second argument must be a pointer to an object of the same quantity type or a quantity derived from the same parent quantity as that of the first argument; this quantity object returns the integer part |
frexp |
| decompose according to the C math.h frexp function; the return quantity type is a variable with identical dimension and unit as the first argument; this quantity object contains the normalized fraction part; the second argument must be a pointer to an object of type int; this quantity object contains the integer power of 2 |
ldexp |
| compose according to the C math.h ldexp function; the return quantity type is a variable with identical dimension and unit as the first argument; this quantity object contains the composed quantity; the second argument must be an object of type int; this quantity object contains the integer power of 2 |
pow |
| raise the first argument to a power which is given as a rational number; the numerator and denominator of the Rational must be compile time constants; in general, the return quantity type differs from that of the original object, except for Rational<1,1> as the second argument; the result is calculated from the value in the standard unit. note: SquareTime is a quantity which holds squared times. |
pow |
| raise the first argument to a power which is given as a rational number, alternative formulation; the numerator and denominator of the Rational must be compile time constants; in general, the return quantity type differs from that of the original object, except for Rational<1,1> as the argument the result is calculated from the value in the standard unit. |
pow |
| raise the first argument to an integral power; the integer must be a compile time constant and is converted into C++ type by Loki's Int2Type; in general, the return quantity type differs from that of the original object, except for Int2Type<1> as the second argument; the result is calculated from the value in the standard unit. note: SquareTime is a quantity which holds squared times. |
pow |
| raise the first argument to an integer power; alternative formulation; the integer must be a compile time constant and is converted into C++ type by Loki's Int2Type; in general, the return quantity type differs from that of the original object, except for Int2Type<1> as the argument the result is calculated from the value in the standard unit. |
pow |
| raise the first argument to a power which is given as an integer or as a double; this is a dynamic version of the pow function, and i or d can be variables at run time; float exponents are also possible; for all cases, also the alternative formulation is provided; in general, the return quantity type differs from that of the original object, except for i = 1 as the second argument; since the exact return type is not known at compile time, the result of pow is returned as a dynamic quantity object (type Dynamic<ST>, where ST is the storage type); this can be assigned to a commensurate variable object or such an object can be copy constructed; if the object assigned to or to be constructed is incommensurate, this is detected at run time and a DimensionMismatch exception (Exceptions) is thrown; the returned dynamic quantity object can not be used directly in further calculations, rather it should be converted immediately into a commensurate variable object; this improves safety when working with such values; the result is calculated from the value in the standard unit; however, any combination of allowed units is possible. Thus,
|
or assigned toMinuteTime t (1.0, Minute()); // construction, 1 min Time t1 (sqrt (t) * sqrt (t)); // square of the square root // should give 1 min // 60 s returned and copied into t1 MinuteTime t2 (sqrt (t) * sqrt (t)); // return value 60 s is copied // into t2 - 1 min
a variable or constant quantity object of a commensurate type.MinuteTime t (1.0, Minute()); // construction, 1 min Time t1 = sqrt (t) * sqrt (t)); // square of the square root // should give 1 min // 60 s returned and assigned to t1 MinuteTime t2 (sqrt (t) * sqrt (t)); // return value 60 s is assigned // to t2 - 1 min
The value of 4.1 min is first recalculated into 246 s, which is then squared to get 60516 s^2. Assignment to a SquareTime (which is in s^2) results in exactly this value, while assignment to SquareMinuteTime (in min^2) results in 50.43 min^2, which is not the expected numerical value of 4.1 x 4.1 = 16.81. However, taking the square root of both these results, we get the expected values.MinuteTime t (4.1); // construction, 4.1 min BSUtilities::Rational<long(2), long(1)> two; // define a rational number 2 SquareTime tsq = pow (t, two); // square t, tsq contains: 60516 s^2 SquareMinuteTime tsqm = pow (t, two); // square t, tsqm contains: 50.43 min^2 Time t_root = sqrt (tsq); // square root of tsq, t_root contains 246 s MinuteTime tm_root = sqrt (tsqm); // square root of tsqm, // tm_root contains 4.1 min
sqrt
and pow
functions, as well as operator/ (divide an object of storage type by quantity object).Of course, the classical definition is followed if assignment is to a variable with the same unit as the first argument of the frexp function:MinuteTime tm (4.251); // construction, 4.251 min int i; // to receive the integer exponent Time t = frexp (tm, &i); // apply frexp function, result // t = 31.885 s, i = 3 Time t1 (3.2); // construction, 3.2 s MinuteTime tm1 = frexp (t1, &i) // apply frexp function, result // tm1 = 0.01333 min, i = 2
MinuteTime tm (4.251); // construction, 4.251 min int i; // to receive the integer exponent MinuteTime tm1 = frexp (tm, &i) // apply frexp function, result // tm1 = 0.531375 min, i = 3
Among the mathematical operations linking two quantity objects, some require that the two objects are commensurate, i.e. for addition.
Chaining of this operator is possible:Time t (60.); // construction, 60 s Time t1 (10.); // construction, 10 s t += t1; // result: t is now 70 s TimeConstant tc (20.); // construction, 20 s t += tc; // result: t is now 90 s
It is, however, required that both arguments are commensurate. This is the case if they are derived from the same parent quantity,Time t (60.); // construction, 60 s Time t1 (10.); // construction, 10 s Time t2 (20.); // construction, 20 s t += t1 += t2; // result: t is now 90 s // t1 is now 30 s
in which case the result is recalculated into the unit of the left hand side argument, or if the right hand side argument is a generated quantity and the two dimensions are identical:Time t (60.); // construction, 60 s MinuteTime tm (1.); // construction, 1 min t += tm; // result: t is now 120 s
If the two argument quantities are non-commensurate, the compiler will catch this error. Note, that incommensurability is assumed if the second quantity object is not generated and has a parent quantity different from that of the first one.Time t (2.); // construction, 2 s SquareTime tsq = t * t; // construction, 4 s^2 t += tsq/t; // result: t is now 4 s t += tsq * t // ERROR
The generated quantity may be only at the right hand side of a += operator, but not on the left hand side, since generated quantities are not design to be assigned to.
Time t (60.); // construction, 60 s Time t1 (10.); // construction, 10 s t -= t1; // result: t is now 50 s TimeConstant tc (20.); // construction, 20 s t -= tc; // result: t is now 30 s Time t2 (20.); // construction, 20 s t -= t1 -= t2; // result: t is now 70 s // t1 is now -10 s MinuteTime tm (1.); // construction, 1 min t -= tm; // result: t is now 0 s
Note that this usage of the atan2 function differs from the other form (atan) in a way that in the present case, dimensioned quantity objects can be used, while this is not possible for the atan form.Time t (5.); // construction, 5 s MinuteTime tmin (0.5); // construction, 30 s double result = atan2 (t, tmin); // calculate result: // 0.165149
Either the first or the second argument, or both, can also be a generated transient quantity object. In this case, however, the dimensions of the two arguments must be commensurate.Time t (7.); // construction, 7 s Time t1 (2.); // construction, 2 s Time t2 = fmod (t, t1); // result: 1 s
Length m (10.0); // construction, 10 m Area A = m * m; // A = 100 m^2
This is even the case if the two objects are of types derived from the same parent quantity (PureNumber is a quantity object which is dimensionless):Time t (5.0); // construction, 5 s Length m (10.0); // construction, 10 m Velocity v = m/t; // v = 2 m s^-1
Time t (5.0); // construction, 5 s MinuteTime tm (1.0); // construction, 1 min PureNumber r = t/tm; // r = 12
Quantity objects which are not related by a parent quantity can not be compared. Consequently, the following is invalid:Time t (60.); // construction, 60 s MinuteTime t1 (1.); // construction, 1 min if (t == t1) // result: true // ..
However, as an exception, comparison to unnamed transient quantity objects is allowed as long as the two quantities are commensurate:Time t (60.); // construction, 60 s Length l (1.); // construction, 1 m if (t == l) // ERROR // ..
Furthermore, dynamic quantity objects may be compared to quantity objects:Time t (1.); // construction, 1 s Time t1 (2.); // construction, 2 s if (t == (t * t1)/t) // comparison: false // ..
Here, the power function returns an object of type Dynamic<ST>, which is compared to the Time object. Such a comparison only makes sense if the two objects are commensurate, i.e. have the same dimension. With dynamic quantity objects, this can only be tested at run time, which decreases performance and may compromise type safety. Thus, such constructs should be used only when dynamic access is absolutely necessary.Time t (1.); // construction, 1s SquareTime tsq = t * t; // value: 1 s^2 double power = 0.5; // define the power (i.e., take square // root) if (t == pow (tsq, power)) // comparison: true // .. if (pow (tsq, power) == t) // also possible // .. if (pow (tsq, power) == pow (tsq, power)) // also possible // ..
For comparison operations, the mode of any of the two quantity objects is not important. Thus, for example, it is allowd to compare a variable quantity onject to a constant quantity object, provided the above conditions are met:
The comparison operations provided by theTime t (1.0); // construction, 1 s TimeConstant tc (5.0); // construction, 5 s if (t == tc) // comparison: false // .. LengthConstant lc (1.); // construction, 1 m if (t <= lc) // ERROR // ..
Quantities
package simply compare the numerical values of the two quantity objects concerned. They do not take into account problems caused by the particular storage type of the value. For example, it is well known that comparison operations of floating point values (in particular the equality operation) can be dangerous and special precautions should be taken to avoid problems. For details, see the following links Quantities
package does not implement any of the schemes described in these references. Thus, if necessary, this has to be included in client code.If ostringstreams (and output file streams) are to be used for output, the corresponding objects have to be constructed first:#include <iostream> Time t (1.0); // construction, 1 s cout << t; // write "1 s" to cout t >> cout; // write "1 s" to cout
This example also demonstrates that output of a quantity object can be intermixed with output of objects of other types.#include <sstream> // include header #include <fstream> // include header std::ostringstream oss; // construct output string stream Time t (5.0); // construction, 5 s oss << "a time quantity: " << t;// write text and "5 s" to stream std::string s (oss.str ()); // extract string from stream // and store it into s std::ofilestream ofs ("f.txt"); // construct output file stream // to file named f.txt Time t (5.0); // construction, 5 s ofs << "a time quantity: " << t;// write text and "5 s" to stream
Since unnamed transient objects do not carry information about the unit, their direct output is incomplete and contains the string "(unknown unit)". Thus, assignment to a commensurate object prior to output is strongly recommended:
Note in the latter, alternative formulation that the assignment has to be included into parentheses.Time t (5.0); // construction, 5 s cout << (t * t)/t; // writes "5 (unknown unit)" Time t1 = (t * t)/t; // writes "5 s" cout << (t1 = (t * t)/t); // alternative, writes "5 s"
Output of quantity objects into a string can be done also directly, without using a stringstream:
Time t (2.0); // construction, 2 s std::string s1; // construct string t >> s1; // s1 contains "2 s" std::string s2; // construct string s2 << t; // s2 contains "2 s"
Furthermore, a conversion of a quantity object into a std::string is provided:
Time t (2.0); // construction, 2 s std::string s1 (t); // construct string, // store "2 s"
The substring representing the value of the variable is converted into the storage type. If this conversion fails, an InputError exception (Exceptions) is thrown:Time t; // default construction, 1 s std::istringstream is ("5.0 s"); // define an input string stream iss >> t; // input from left: t = 5 s std::istringstream is1 ("6.0 s"); // define an input string stream t << iss1; // input from right: t = 6 s "7.0 s" >> t; // input from left: t = 7 s t << "8 s"; // input from right: t = 8 s
If the unit symbol does not correspond to one of the quantity's unit's symbols, also an InputError (Exceptions) is thrown:Time t; // default construction, 1 s t << "xxxx s"; // InputError thrown
If the unit is omitted in the input information, the storage unit of the variable object is assumed:Time t; // default construction, 1 s t << "3.5 x"; // InputError thrown
For input from a string, there is an alternative formulation, which allows an assignment-type syntax:Time t; // default construction, 1 s t << "60"; // read without unit: t = 60 s MinuteTime tm; // default construction, 1 min tm << "60"; // read without unit: tm = 60 min
Time t; // construction, 1 s t = "720 s"; // assign, t = 720 s MinuteTime tm; // construct, 1 min tm = "10 min"; // assign, tm = 10 min tm = "30 s"; // re-assign, tm = 0.5 min
Note, that if input of name and/or symbol of the quantity is requested by the respective control objects (Control Objects for Input and Output), these items must be present in the input. If they are absent in these cases, an InputError exception (Exceptions) is thrown.
The control objects excert their function by being placed in a stream as shown by the examples below. If several output or input streams are used, they affect all streams for which they are defined. Thus, if a control object is placed in the global cout
stream, it will also affect output into an output file stream. Output control objects do also have an effect on conversion of a quantity object into a std::string (see Output Operations with Quantity Objects). Similarly, if placed in cin
, a control object also affects input through an input string stream or other input possibilities.
Instances of the following control objects can be placed in an output stream.
PrintName<true>
() -- switch on output of quantity name. Subsequent output of a quantity will include the quantity object's name. Time t (1.0); // construction, 1 s t.name ("time"); // set name to "time" cout << t << endl; // prints "1 s" cout << PrintName<true> () << t << endl; // prints "time = 1 s"
PrintName<false>
() -- switch off output of quantity name. Subsequent output of a quantity will not include the quantity object's name (default). PrintSymbol<true>
() -- switch on output of quantity symbol. Subsequent output of a quantity will include the quantity object's symbol. Time t (1.0); // construction, 1 s t.symbol ("t1"); // set symbol to "t1" cout << t << endl; // prints "1 s" cout << PrintSymbol<true> () << t << endl; // prints "t1 = 1 s"
PrintSymbol<false>
() -- switch off output of quantity symbol. Subsequent output of a quantity will not include the quantity object's symbol (default).
Note, that placing such objects in an std::istream will cause a compile time error.
Instances of the following control objects can be placed in a std::istream:
ReadName<true>
() -- switch on input of quantity name. If this control object has been used, all subsequent inputs must include a quantity name and an equal sign (default), until input of a name is explicitly switched off. Not providing a name and an equal sign will result in an InputError. Requesting the equal sign can be switched off with ReadEqual<false> (see below). Time t; // default construction, 1 s t = "2.5 s"; // input without name, t = 2.5 s cin >> ReadName<true> (); // switch input of quantity name on t = "timename = 2.5 s"; // sets also the name t = "2.5 s"; // throws InputError t = "timename 2.5 s"; // throws InputError
ReadName<false>
() -- switch off input of quantity name. Subsequent input of a quantity will not expect the quantity object's name (default). ReadSymbol<true>
() -- switch on input of quantity symbol. If this control object has been used, all subsequent inputs must include a quantity symbol and an equal sign (default), until input of a symbol is explicitly switched off. Not providing a symbol and an equal sign will result in an error. Requesting the equal sign can be switched off with ReadEqual<false> (see below). Time t; // default construction, 1 s t = "2.5 s"; // input without name, t = 2.5 s cin >> ReadSymbol<true> (); // switch input of quantity symbol on t = "timesymbol = 2.5 s"; // sets also the symbol t = "2.5 s"; // throws InputError t = "timesymbol 2.5 s"; // throws InputError
ReadSymbol<false>
() -- switch off input of quantity symbol. Subsequent input of a quantity will not expect the quantity object's symbol (default). ReadEqual<true>
() -- switch on input of quantity equal sign. Subsequent input of a quantity will expect an equal sign between the quantity object's name and symbol and the value, if name or symbol reading is switched on (default). Time t; // default construction, 1 s t = "2.5 s"; // input without name, t = 2.5 s cin >> ReadSymbol<true> (); // switch input of quantity symbol on t = "timesymbol = 2.5 s"; // sets also the symbol t = "2.5 s"; // throws InputError t = "timesymbol 2.5 s"; // throws InputError cin >> ReadEqual<false> (); // swith off request for "=" t = "timesymbol 2.5 s"; // now this is ok
ReadEqual<false>
() -- switch off input of quantity equal sign. Subsequent input of a quantity will not expect an equal sign between the quantity object's name and symbol. ReadUnit<U>
() -- set unit U to be assumed for input of a quantity. Throws anTime t; // default construction, 1 s t = "2.5"; // input without unit, t = 2.5 s cin >> ReadUnit<Minute> (); // switch interpretation to Minute t = "2.5"; // now t = 150 s
InputError
if the symbol of U is not found in the list of symbols allowed for the quantity object. Time t; // default construction, 1 s t = "2.5"; // input without unit, t = 2.5 s cin >> ReadUnit<Metre> (); // switch interpretation to Metre t = "2.5"; // throws InputError, Metre not allowed // for Time
ReadUnit<NoUnit>
() -- switch off any assumption of unit for input of a quantity. Time t; // default construction, 1 s cin >> ReadUnit<Minute> (); // switch interpretation to Minute t = "2.5"; // input without unit, t = 150 s cin >> ReadUnit<NuUnit>; // switch off interpretation as min t = "2.5"; // now t = 2.5 s
Note, that placing such objects in an std::ostream will cause a compile time error.
Note also, that the implementation does not distinguish between a string in the input being a name or a symbol for a quantity object. All tokens in the input will be read subsequently and interpreted in the order name, symbol, equal sign, value, and unit symbol.
You can generate a new (possibly transient) quantity object with the recalculated value:.
The two alternatives should give the same result.Time t(60.0): // construction, 60 s MinuteTime tm = t (Minute ()); // return value in min, // new transient object returned, // tm = 1.0 min MinuteTime tm1 (t (Minute ())); // return value in min, // new transient object returned, // tm1 = 1.0 min; alternative
Similarly, the conversion can be triggered by a string which corresponds to a symbol string of a unit, which is associated with the parent quantity of the quantity to be converted:Time t(60.0); // construction, 60 s double t_min = t.value (Minute ()); // return value in min, // no new object generated, // t_min = 1.0
Time t(60.0); // construction, 60 s double t_min = t.value ("min"); // return value in min, // no new object generated, // t_min = 1.0
value
() returns the value in the storage unit Time t(1.0); // construction, 1 s double t1 = t.value (); // t1 = 1.0 MinuteTime tmin(1.0); // construction, 1 min double tmin1 = tmin.value (); // t1 = 1.0
standard_value
() returns the value in the storage unit Thus, in the second example, the contents of the MinuteTime object is 1 min, but the return value is 60 (after recalculation into the standard unit s).Time t(1.0); // construction, 1 s double t1 = t.standard_value (); // t1 = 1.0 MinuteTime tmin(1.0); // construction, 1 min double tmin1 = tmin.standard_value (); // t1 = 60.0
value
(unit) returns the value in unit; an object of unit is passed as argument The unit must be a valid unit of the parent quantity.Time t(60.0); // construction, 60 s double t1 = t.value (Minute ()); // t1 = 1.0 MinuteTime tmin(1.0); // construction, 1 min double tmin1 = tmin.value (Second ()); // t1 = 60.0
value
(unitsymbolstring) returns the value in unit; a string is passed as argument The string must match a unit symbol of a valid unit of the parent quantity. Note, that this function dynamically checks the supplied symbol string. If the symbol is not found as any of the symbols in the units associated to the quantity in question, a UnitMismatch exception (see Exceptions) is thrown.Time t(60.0); // construction, 60 s double t1 = t.value ("min"); // t1 = 1.0 MinuteTime tmin(1.0); // construction, 1 min double tmin1 = tmin.value ("s"); // t1 = 60.0 double tmin1 = tmin.value ("xxx"); // ERROR; UnitMismatch
The functions in the first set, unitsymbol
() and unitname
(), allow dynamic access to the requested information. They can be called on a particular object, i.e. an instance of a quantity class.
unitsymbol
() returns the string of the unit symbol Time t(1.0); // construction, 1 s std::string symbol = t.unitstring (); // string contains "s"
unitname
() returns the name of the unit symbol MinuteTime t(1.0); // construction, 1 s std::string name = t.unitname (); // string contains "minute"
Unitsymbol
() and Unitname
(), allow static access to the requested information. They can be called on a class name, which is known at compile time, without having to create an instance of that class. Unitsymbol
() returns the string of the unit symbol std::string symbol = Time::Unitstring (); // string contains "s"
Unitname
() returns the string of the unit symbol std::string name = Time::Unitname (); // string contains "second"
symbol
() reports the symbol of the quantity object. Per default, after construction, the symbol string of the object is empty. However, the symbol may be set (see Status Change Operations with Quantity Objects).Time t(3.0); // construction, 3 s t.symbol ("t_1"); // set the symbol of t std::string symbol = t.symbol (); // symbol contains "t_1"
name
() reports the name of the quantity object. Per default, after construction, the name string of the object is empty. However, the name may be set (see Status Change Operations with Quantity Objects).Time t(3.0); // construction, 3 s t.name ("time"); // set the name of t std::string name = t.name (); // name contains "time"
IsDimensionless
provides static access to query a class whether it is dimensionless. It can be called on a class name, which is known at compile time, without having to create an instance of that class. bool dim = Time::IsDimensionless (); // dim is true
isDimensionless
() provides dynamic access to query a class whether it is dimensionless. It is called on an instance of the class. Time t(1.0); // construction, 1 s bool dim = t.isDimensionless (); // dim is true
The dynamic routines listed in this section are virtual. Thus, it is possible to generically work with pointers to a Quantities object, and still retrieve the desired information.
Time t(1.0); // construction, 1 s ::Quantities::Quantities *q = &t; // store generic pointer to t bool dim = q->isDimensionless (); // dim is true
name
sets the name string in a quantity object. It uses an argument of type std::string. The conversion of a character string into a std::string can be used to circumvent the explicit definition of a name string:Time t; // default construction, 1 s std::string namestring ("time"); // define a string t.name (namestring); // now t has name "time"
Time t; // default construction, 1 s t.name ("time"); // now t has name "time"
symbol
sets the symbol string in a quantity object. It uses an argument of type std::string. The conversion of a character string into a std::string can be used to circumvent the explicit definition of a symbol string:Time t; // default construction, 1 s std::string symbolstring ("t"); // define a string t.symbol (symbolstring); // now t has symbol "t"
Time t; // default construction, 1 s t.symbol ("t"); // now t has symbol "t"
The version
() function returns a std::string with some information about the version of the currently used implementation and the versions of some packages it depends on. For example, this information can be placed in an output stream:
Time t(1.0); // default construction, 1 s cout << t.version () << endl; // prints a line with version // information
The following low level functions and operations on or with quantity objects are not recommended for direct use in user code. They are, however, frequently used in the Quantities code itself.
print (ostream)
print_value (os)
read_value (str)
UnitPointerBySymbol<>
UnitIndexBySymbol<>
ListUnitSymbols<>
AllUnits<>
DefaultUnit<>
GenerateVariable<>
SameDimensioned<>
CheckSecondDimension<>
CheckUnit<>
CheckUnits<>
Unit::name ()
Unit::symbol ()
Unit::is_SI ()
Unit::Name ()
Unit::Symbol ()
Unit::Is_SI ()
Unit::version ()
Unit::Version ()
exact ()
ratio ()
dynamic_standardize ()
dynamic_reverse ()
Standardize<>
Reverse<>
Quantity::Name ()
Quantity::Symbol ()
further helper functions in Unit and Dimension classes
new
should later be explicitly deleted: Time *t = new (Time); // ... use pointer to Time object t delete t;
Quantities
makes frequently use of metaprogramming techniques. Thus, the C++ compiler is internally used to perform calculations during compile time. For example, the compiler is instructed to check commensurability properties or the correctness of units. In this way, errors at run time can be minimized.
DimensionError<true>
should occur in the compilers output, if incommensurability of dimensions is detected.
AssingmentError<true>
should occur in the compilers output, if assignment to a generated quantity is detected.
UnitError<true>
should occur in the compilers output, if an inappropriate unit has been used. Note, that depending on the compiler used, these messages may be buried in a large number of output lines, and are often not easily found.
The following exceptions could be thrown:
InputError
is thrown by input functions, if the input does not conform to the requirements, e.g. the input string is too short or does not contain requested substrings.
UnitMismatch
is thrown if an operation (often a conversion) involving a unit is requested and the unit is not in the list of units available for the quantity objects type, or its parent quantity's type.
DimensionMismatch
is thrown if an operation involving a Dynamic quantity is requested and the dimensional information is incommensurate.
VectorOutOfBounds
(to be written) Alternatively, the more specific exceptions can be caught:Dynamic q; int exp; exp = 1; // no exception expected try {q = pow (q, exp);} // raising to power of 1 is ok catch (QuantityError) { // handle exception } exp = 2; // exception expected try {q = pow (q, exp);} // dimensions are incommensurate catch (QuantityError) { // handle exception }
QuantityError allows to transport a textual message. This message may be set when the exception is thrown. If the code does not set the message itself, a default text is provided. The message can be used when handling the caught exception:Dynamic q; int exp = 2; // DimensionMismatch expected try {q = pow (q, exp);} // dimensions are incommensurate catch (DimensionMismatch) { // handle exception }
try { // ... // try some code } catch (DimensionMismatch error) { std::cout << error.message () << std:: endl; // print out the text message }
The PhysicalQuantities
library provides implementations of various quantities, which are important in scientific work, on the basis of the Quantity
library. These physical quantities are selected from the SI, and the library defines the most important units, including the SI unit, and if applicable other units in common use. In the present version, the quantities listed in the following table are implemented. In this table, also, the dimensions (in the form of the power septuple) and the units defined are given
quantity with parent quantity | derived quantities | dimension | unit(s) |
acceleration AccelerationQuantities | Acceleration AccelerationConstant AccelerationVector | 1, 0, -2, 0, 0, 0, 0 | Gal standard acc. of free fall metre per square second |
... | ... | ., ., ., ., ., ., . | ... |
[2] H. Sutter and A. Alexandrescu, C++ Coding Standards, Addison-Wesley, Boston, 2005, p. 4.
[3] S.C. Drewhurst, C++ Gotchas, Addison-Wesley, Boston, 2003, p. 129, p. 153 ff.
[4] N.M. Josuttis, The C++ Standard Library. A Tutorial and Reference, Addison, Wesley Longman, Reading, 1999.