CFUnits
Omega fields carry their units as a string attribute in the plain form used
by the CF standard name table: unit symbols separated by single spaces, each
with an optional signed integer exponent (m s-1, kg m-3, W m-2), and 1
for a dimensionless quantity. When a field is derived from others, its units
must be derived too. The CFUnits class parses such a string, supports the
product, quotient and integer power of units expressions, and formats the
result back in the plain form.
The string interfaces cover the common cases:
#include "CFUnits.h"
std::string Flux = CFUnits::multiply("m s-1", "m2"); // "m3 s-1"
std::string Speed = CFUnits::divide("m3 s-1", "m2"); // "m s-1"
std::string Energy = CFUnits::power("m s-1", 2); // "m2 s-2"
Exponents of the same symbol are merged (m s-1 times m is m2 s-1) and
a symbol whose exponent reaches zero is dropped, so kg m-3 times m3 is
kg. In the result, symbols with positive exponents come first, then those
with negative exponents, each in the order they first appeared.
A units string may also start with a positive number, the scale factor of a
scaled unit as udunits writes it: 1e+06 m3 s-1 is a million cubic meters
per second. Multiplying a field’s values by a constant, for example to report
a transport in units of 10^6 m3 s-1, scales its units by the reciprocal:
std::string Transport = CFUnits::scale("m3 s-1", 1.0 / 1.0e-6); // "1e+06 m3 s-1"
Scale factors multiply through products, quotients and powers, and are
written with the fewest digits that reproduce them, so the rounding error in
1.0 / 1.0e-6 does not show. A number is only accepted before the unit
symbols, and must be positive and finite.
A field with no units attribute has unknown units, represented by the empty string. Unknown units propagate through every operation, so a field derived from one without units has no units either, rather than wrong ones.
The parser also accepts the udunits caret and slash spellings (m^2, m/s),
which some older field definitions still use, but always emits the plain
form. Any other string is an error: the string interfaces abort, and the
value interface reports it so a caller can decide:
CFUnits Units;
Error Err = CFUnits::parse("m s-1", Units); // Fail for a malformed string
CFUnits Area;
CFUnits::parse("m2", Area);
std::string Flux = (Units * Area).str(); // "m3 s-1"
CFUnits also provides operator/, pow(int), scaled(double),
isUnknown(), isDimensionless() and an order-insensitive operator==.
A malformed units string is a programming error in a field definition, and aborting when the derived field is created stops the run at initialization rather than writing output with a wrong or missing attribute.