Boost logo

Boost :

From: Matthias Schabel (boost_at_[hidden])
Date: 2007-01-16 17:00:42


Janek,

> become the same template. But in fact, maybe following Ockham's
> razor you should just follow your suggestion and use
>
> template<class Unit,class Y = double> class quantity;
>
> because 'class Unit' *cannot* have a reasonable default. While class
> Y=double *can* have a default.

I agree that that's the most sensible choice. Since there's really no
gain, and potentially some compile-time cost on top of a library
that's already compile-time heavy, I'll just flip the order of the
template arguments for the quantity class and default to double
precision value_type.

> I hope that you have read the reviews of Andy's Little quantity
> library, and why it was rejected. Some of the reasones were
> considered show-stoppers. I remember few of them, but better you will
> read the reviews.

I followed the debate fairly closely, and saved a number of the
posts. I have made an effort to address as many issues as seemed
feasible without experiencing extreme "feature creep"... I don't
think it is reasonable to try to address every conceivable
application in unit conversion with this library, so I am targeting a
flexible system that can form a foundation for dimensional analysis
that can be used to build more specialized user libraries.

> 1) support for powers of N (where N is different from 10) in
> multipliers: usually people operate using kilo (10^3), mega (10^6).
> But sometimes a unit is expressed in different system, like a power
> of 2 (harddrive capacity, file size: http://en.wikipedia.org/wiki/
> Kibi )

My approach to this is to delegate this behavior to a value_type that
handles powers (see scaled_value.hpp and unit_example_6.cpp). I have
not defined prefixes for metric or binary prefixes in that file, but
that would be easy. The rationale for this approach is that a
kilometer in the MKS system really is 1000 meters, just as a kilobyte
is 2^10 bytes... Therefore, it is more a question of how to preserve
precision in numerical computations using values that may have large
power values (nanometers/femtoseconds etc...), which falls to the
value_type.

> C) only those multipliers that are declared either by the user, or
> in an included file that contains some predeclared multipliers are
> used by the program. Allow the user to chose between explicit and
> implicit conversion, when he wants. You don't know if someone wants
> to store 1km or 1000m in his variable.

As I alluded to above, if the user wants 1km, it can be done like this:

quantity< scaled_value<double,10,3>,SI::length> q1
(scaled_value<double,10,3>(1.0)*meter);

while 1000m could be either

quantity<double,SI::length> q2(double(1000)*meter);

or

quantity< scaled_value<double,10,0>,SI::length> q3
(scaled_value<double,10,0>(1000.0)*meter);

If you don't care about the potential loss of precision, of course
you can just define prefix factors as constants (like I do in
si_units.hpp):

static const long double kilo(1e3);

quantity<double,length> q2(1*kilo*meter);

Part of the problem is that there are many equally reasonable needs
to address specific domains; it isn't possible to accommodate all of
these in a single library. That's why I've tried to make this library
as flexible as possible to facilitate
add-on libraries dealing with special requirements.

> 3) Do not eforce SI on anyone:

I absolutely agree. This library is completely system agnostic, with
SI (and CGS) systems provided for convenience.

> Make it easy to declare one's own unit system. This point it is
> not about imperial and metric. It is about physics. For instance
> parsecs and light-years do not belong to SI. But try to store 100
> parsecs with 'double' precision using meters. (or yottameters 10^24
> (?)). This is not practical, any calculations that operate on parsecs
> must be done in parsecs, or we lose precision.

This forms the core of the example documentation, in which a mini-SI
system is implemented.
The code is in test_system.hpp. If you look at the documentation, you
can see how to do
completely general dimensional analysis on any units you choose to
define.

> Of course you can provide few #include <boost/unit/SI.hpp>,
> #include <boost/unit/imperial.hpp>, #include <boost/unit/
> relativity.hpp>,
> etc files. But you never know what kind of unit system will be useful
> for somebody.

This was a major design consideration for the library - the whole
header file for test_system.hpp
is only about 50 lines of code, including IO, various convenience
typedefs, etc... - basically everything
one would need to define for a unit system with length, time, and
mass only... Physics-based systems
such as various natural units should be easy to implement. Systems
such as imperial units are not
intrinsically harder, but are left to the interested individual
because they do not actually define any
base units - is the fundamental unit of length in imperial units the
inch? the foot? the yard? the mile?
The same problem arises for mass, area, etc...

> It should be equally easy to declare that I will use a meter (1
> line)
> and a second (1 line, makes 2 lines total) in my program, instead
> of including whole SI.hpp (1 line), which I don't need.

This would just involve increasing the granularity of the SI header
to split it up into mini-headers for
each fundamental unit:

<boost/units/systems/SI.hpp>

would become

#include <boost/units/systems/SI_length.hpp>
#include <boost/units/systems/SI_mass.hpp>
#include <boost/units/systems/SI_time.hpp>

etc... Certainly easy enough to implement this, since there is no
coupling between fundamental units. This fact, which
I should probably make clearer, also makes it easy for the user to
add new fundamental units to existing unit systems:

#include <boost/units/systems/SI.hpp>

namespace SI {

struct foo_tag : public ordinal<10> { };

typedef dimension< boost::mpl::list<dim<foo_tag,static_rational<1> >
> >::type foo_type;

typedef unit<system,foo_type> foo;

static const foo foo_unit;

}

quantity<double,SI::foo> q1(1.5*foo_unit);

q1*quantity<double,SI::length>(1.0*meter) -> 1.5 foo meter

Adding preprocessor shortcuts would make this even simpler, though I
suspect that the number of users who will implement their own unit
systems will be a small minority...

> 4) Make input/output as decoupled as possible. Basic cout << of the
> unit is enough for beginning. Do NOT make it sophisticated in any
> way, otherwise you will start writing another library.

This is what is already implemented. I demonstrate interoperability
with Boost.Serialization in one of the examples. I'm happy to stop
there for now...

> 5) leave door open for adapting your library into other math
> libraries. For example, let's say that I have a 3D vector of meters:
>
> vector<3,quantity<meter> > a,b;
> vector<3,quantity<meter*meter> > c;
>
> c=dot_product(a,b);
>
> What is the return type of function dot_product ? Think about it, and
> you will see what I mean. Where that function is defined? Well, boost
> still has not a small vector library, but you should leave an open
> door
> for making one.

I've dealt with this quite carefully in the existing library; take a
look at

unit_example_7.cpp for interoperability with my own array class
(output only, since the array class code is not included)
unit_example_8.cpp for interoperability with Boost.Quaternion
unit_example_9.cpp for interoperability with std::complex and a toy
reimplementation of complex that handles operators correctly

The latter example shows some of the problems that can arise when
heterogeneous operators are not implemented by the container class.
For example, in the case you provide, to be completely general, the
return type of the dot_product function should be

template<class A,class B>
add_typeof_helper<
multiply_typeof_helper<A,B>::type,multiply_typeof_helper<A,B>::type
>::type
dot_product(const vector<N,A>& a,const vector<N,B>& b)

In your particular case, quantity<meter*meter> is correct, but not in
general... This algebra is (to the best of my knowledge) correctly
implemented in the library as it stands. For new value_types with
unconventional algebras, the following helper classes need to be
specialized:

add_typeof_helper
subtract_typeof_helper
multiply_typeof_helper
divide_typeof_helper
power_typeof_helper
root_typeof_helper

so the compiler can determine the result type of an algebraic
expression involving that type.

> Ok, I don't remember any more. Better you will have a look at that
> reviews. I still have them in my inbox, so in fact I can forward them
> to you.

I'd appreciate that; thanks for the input. If you get a chance to
look at the library itself, I'd appreciate your opinion...

Matthias


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