Boost logo

Boost :

From: Terje Slettebø (tslettebo_at_[hidden])
Date: 2003-05-01 17:06:30


(This is sent as Unicode, as it contains special symbols. If there's
problems reading this, I can resend without it)

>From: "Paul A. Bristow" <boost_at_[hidden]>

> | [mailto:boost-bounces_at_[hidden]]On Behalf Of Terje Slettebø
>
> | Actually, the one I'm working on ...
>
> This is all very interesting (hoping to encourage you by indicating that
someone
> is listening and valuing this work:-).

I appreciate it. :)

Like with output operators for containers, etc., there are several existing
approaches to this, as well. It certainly encourages experimentation.
Perhaps a good solution could come from all of this.

I intend to put it in the Sandbox, when it's ready, so people may take it
for a spin. :)

As mentioned, the work on this predates the current Boost thread, as I
experimented with a version about a year ago. When the issue has come up a
few times, later, I've gone a little back to it. In summary, I've restarted
several times.

When the current thread started, I started from scratch. :) That enabled me
to move quickly forward, building on the earlier experience.

> | With this, you may use appropriate prefixes, e.g. "k", for "km", above,
etc.
> | The value stored is still stored the same way, though; the prefix only
> | matters for initialisation, and reading the value for output.
>
> It would be even nicer if input could be handled similarly, so we can ask
the
> user to input length, and correctly decode replies like 1.23 km, or 1.23
um, or
> even 1.23 inch, 1.23 yard for example.

Yes, it would be possible, but it seems to then require a string as input,
as "<identifier> <identifier>" (like "1.23 km") can't be used in C++ (unless
you overload whitespace. ;) ).

This would of course be a possibility, if that's what you suggest, e.g.:

length<double> value="1.23 km";

It would then have to decode this, before use.

I'm already using Spirit for processing formatting strings for output, so it
could be possible to use it for input, as well.

The output formatting is decoupled from the type to be output, so anybody
may write another output routine. To make it easy to use, it includes an
output routine, which gives a lot of flexibility over the output. Like the
stream operators, it uses a manipulator to set formatting.

I've worked a lot on making the output formatting flexible, yet simple to
use. The formatting is way more difficult than e.g. date/time formatting,
because you have several ways of writing the quantity components. Therefore,
if the following appears complex at first, please bear over with me until
you see the output examples below it. Also, there's a default format, and
any and all of the parameters to the format() function may be defaulted.

The formatter has the following parameters (the name "format" is open to
change, especially as Boost.Format already use it. :) ):

format(general, multiply_symbol, division_symbol, exponent_symbol, quantity)

"general" is a string which specifies the formatting, where format
specifiers ("%<symbol>") may be used, similar to date/time formatting. It
can take the following specifiers:

%v - Value (excluding prefix-part, i.e. for 1 km, this is 1)
%V - Value (full value, 1 km gives 1000)
%p - Prefix symbol
%P - Prefix name
%u<N> - Unit symbols (the value <N> selects the "unit set")
%U<N> - Unit names (ditto)
%q - Quantity

The default for format is: format("%v %p%u0", "∙", "/", "^", "");

Some examples:

// unit<kg, m, s, A, K, mol, cd>

typedef dim<double, unit<1,2,-3,-1,0,0,0>, prefix<3> > quantity; //
Mg∙m^2/(s^3∙A) (Note: kg + k = Mg)

quantity value(1.23);

// The first using the above default format

std::cout << value; // Output: "1.23 Mg∙m^2/(s^3∙A)"

// "1.23" is the value, "M" is the prefix symbol, and "g∙m^2/(s^3∙A)" is the
unit

// An empty division symbol means use exponent form, also for negative
exponents, rather than fraction form:

std::cout << format("%v %p%u0", "∙", "") << value; // Output: "1.23
Mg∙m^2∙s^-3∙A^-1"

std::cout << format("%v") << value; // Output: "1.23"

std::cout << format("%V %u0") << value; // Output: "1230 kg∙m/(s^3∙A)"

std::cout << format("%v %P%U0", " * ", " / "); // Output: "1.23 megagram *
meter^2 / (second^3 * ampere)"

And, to show off what "unit set" is all about (note "u1", rather than "u0",
in the following), and the "quantity" parameter:

std::cout << format("%v %p%u1 %q", "∙", "/", "^", "(%q)") << value; //
Output: "1.23 kV (electric potential difference)"

Nifty? ;)

(Mg∙m^2/(s^3∙A) = kV)

Some other examples:

// Demonstrating ohm's law: U=R * I

ohm<double> r(10); // 10 Ω. May also use "electric_resistance<>"
ampere<double> u(2); // 2 A. May also use "A<>" or "electric_current<>"

std::cout << u/r; // Output: "2 V" or "2 volt", or "2 kg∙m^2/(s^3∙A)", or
something else, depending on the format setting

// Non-SI units could also be supported:

feet<double> length(10);

std::cout << length * length; // Output "9.29 m^2" (but see below)

All values are stored internally as SI units (kg, m, s, etc.), which means
there's no run-time overhead for using different units, prefixes, etc. in an
expression. It only matters when a variable is initialised with a numeric
value (like "10", above. This is stored internally in units of meter, 10 ft
= 3.048 m), and for printing the value, where it's converted back to the
original unit.

Because all calculations are in terms of SI units, any results are also in
SI units. To get a result in non-SI units, it needs to be cast to that (this
is safe - only valid casts are allowed):

std::cout << (square_feet<double>) length * length; // Output: "100 ft^2" or
"100 feet^2", or some other format.

Extra dimensions are supported, as well, and their meaning is left up to the
user of the library, who defines units with them, so you may have:

// frequency = unit<0,0,-1,0,0,0,0>
// angular_velocity = unit<0,0,-1,0,0,0,0,1> // Extra dimension used for
"angle"

frequency<double> value1;
angular_velocity<double> value2;

result = value1 + value2; // Error, not the same unit

std::cout << value1; // Output: "1.23 Hz"
std::cout << value2; // Output: "1.23 rad/s"

You may also treat angular_velocity as a synonym to frequency, by defining
it this way. This is up to the library user, who may define any unit,
prefix, etc.

I'm also careful not to use partial specialisation, so it should work even
on MSVC 6/7. :)

Regards,

Terje


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk