Boost logo

Boost :

From: Eric Ford (azsro001_at_[hidden])
Date: 2003-03-07 05:10:05


> I have found in the past that there is a need to
> distinguish between dimension handling and unit handling.
>
> In the case of a physical calculation, I usually ensure that
> the code works with a self consistent set of units, and thus
> only need dimensionality, compile time, code.
>
> Units then become important only in the user interface, and
> I have a list of the units used in the physical calculation
> for each dimension.
>
> This is obviously only one way of using dimensions and units,
> but I think an important one. The usual exception to the above
> is for money where you are working with multiple currencies.

I agree that this is an important distinction which some people get
confused on. Reviewing our old discussions, this was the closest
thing to a concensous. Money was the one issue that was still very
much up in the air. My conclusion was that money probably warranted a
whole library to itself, but that many users would be happy with a
simple money dimension tacked onto SI.

> A couple of use cases, that I am unsure would be handled by the
> current proposal:

> I have a "Cost" class that records costs for different factories
> in different countries. The "unit" used for each is factory
> is different, eg USD for factories in US, GBP for those in UK.
> ie the unit varies per instance.

I agree that treating value as the dimension, but I choose to use
currency as the name of the dimension, since value has other
connotations to programers. There are several ways you could deal
with this with my draft library. Users would choose the most
appropriate way for their particular purposes. One way involves
treating USD, GBP, etc. as units for the currency dimension. The
other involves treating USD, GBP, etc. as qualifiers for the type of
currency.

One way is to decide that what you're really interested in is the
value, and you're not really interested in which currency that is in.
Such a user could use the currency dimension something like this...

static const currency usd (1.00);
static const currency gbp (1.50);

currency chicago_cost = 2000 * usd;
currency dublin_cost = 1500 * gbp;
currency total_cost = chicago_cost + dublin_cost;
cout << "The total cost is " << total_cost << " = " << total_cost / usd << " USD = " << total_cost / gbp << " GBP\n";

The advantage of this way is that the user can do arithmetic between
different currencies transparently. If all the user is interested in
is the value in their home currency, then this may be adequate and
even desirable.

Of course, there are cases where this would not be appropriate. For
example, if a user wants to keep track of how much of his money is in
different currencies, a common desire given that the relative values
of currencies fluctuate. In such a case, I would recommend making use
of my qualifier tags. Something like...

char usd_label[] = "USD";
char gbp_label[] = "GBP";

typedef basic_qualifier<usd_label> usd_qualifier;
typedef basic_qualifier<gbp_label> gbp_qualifier;
typedef basic_qualifier<euro_label> euro_qualifier;

typedef qualified_dimension<currency,usd_qualifier> usd;
typedef qualified_dimension<currency,gbp_qualifier> gbp;
typedef qualified_dimension<currency,euro_qualifier> euro;

static const usd dollar (1.00);
static const gbp gb_pound (1.00);

usd chicago_cost (2000.); // just to show another way
usd la_cost = 1800. * dollar;
gbp dublin_cost = 1500. * gb_pound;

usd total_us_cost = chicago_cost + la_cost; // same currency, so ok
usd total_cost = total_us_cost + dublin_cost; // different currencies: WILL NOT COMPILE BY DEFAULT

This way the compiler will prevent you from assigning or performing
arithmetic between currencies (unless a user want to allow such
operations and specializes the is_tag_change_ok<> and/or
promote_dimension_qualifier<> templates accordingly).

Now a user who wants to keep track of all her bank accounts in
different currencies will do so with strongly typed variables.
Of course, they might want to write a class that allows for
combining these into a single value in some time dependant fashion
(and most likely not possible at compile time).

> A similar situation occurs for "dimensions" of physical units.
> eg. if you want a list of quantities used per output weight
> or output pieces, then each item in the list has different
> dimensions, eg. Watt seconds/time, Litre/piece, number/kg.
> In this case I obviously have to give up the type checking,
> but still need to be able to record a dimension, to allow
> display in appropriate units.

I think this could be addressed with something like

list<any> materials;
materials.push_back(100.*joule);
materials.push_back(50.*liter);

cout << "This will take: "
for(list<any>::iterator i = materials.begin();i!=materials.end();++i)
  cout << " " << *i << "\n";

Now, it's probably obvious that the 100 joules is of electricity, but
may not be so obvious what the 50 liters is of. If you'd like to keep
track of what those fifty liters are of, then I'd recommend you use a
qualifier tag like:

char garlic_label[] = "garlic";
typedef basic_qualifier<garlic_label> garlic_qualifier;
...

materials.push_back(qualify<volume,garlic_qualifier>(50. * liter));

I hope this makes sense and addresses some common concerns.

BTW- Thanks to all those who have offered help with type promotion,
the time issue, and testing with MSC++. Provided boosers basically
support my draft's layour, I will try to incorporate what I learn from
your posts into the next version I post.


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