From: si-units-list@si-units (si-units-digest)
To: si-units-digest@si-units
Subject: si-units-digest V1 #17
Reply-To: si-units-list@si-units
Sender: si-units-list@si-units
Errors-To: si-units-list@si-units
Precedence: bulk
si-units-digest Thursday, May 15 2003 Volume 01 : Number 017
In this issue:
Re: SI Units Project: printing units?
Re: SI Units Project: [Fwd: ACCU SI -- printing]
RE: SI Units Project: [Fwd: ACCU SI -- printing]
RE: SI Units Project: printing units?
Re: SI Units Project: printing units?
Re: SI Units Project: [Fwd: ACCU SI -- printing]
----------------------------------------------------------------------
Date: Wed, 14 May 2003 22:36:05 +0200
From: =?iso-8859-1?Q?Terje_Sletteb=F8?=
Subject: Re: SI Units Project: printing units?
I've been reading up on the postings in this thread, going way back to
February.
>From: "Watts, Simon (UK)"
> Time for a discussion on how to implement printing of units?
>
> My approach so far (conceptually at least) has been with the automatic
> determination of compound symbols from the unit type, such that:
>
> cout << 1.0 * kilogram * metre / (second * second) << endl;
>
> gives:
> "1.0 N"
That's good. Some code I work on does this, as well.
> where the unit selected is not based solely on the dimensionality, but
also
> on how the dimension is defined (hence the dimension_trait stuff in my
units
> code). This allows the following:
>
> cout << 1.0 * hertz << ", " << 1.0 * bequeral << endl;
>
> giving:
> "1.0 Hz, 1.0 Bq"
>
> (Hertz and Bequeral are both time^{-1)).
And this. However, in my system, the difference isn't enforced, so it _is_
be possible to add hertz and becquerel, even though it wouldn't make much
sense. As I understand, your system treats them as distinct, and hence
prevents what's probably an error.
> This system would make heavy use of stream manipulators, to set the symbol
> table to be used, and any formatting options.
>
> The symbols selected would impact on the value displayed:
>
> cout << si::symbols // use SI symbol table
> << 1000.0 * metre // "1.0 km"
> << 1000.0 * metre * metre // "1000.0 m^2"
> << imp::symbols // use Imperial Brit. symbols
> << 1.0 * metre // "3.28 ft"
> << 1000.0 * metre // "1093.6 yd"
>
> [[ The default symbol table would be 'none', symbols not being displayed,
> and units::no_symbols would remove any symbol table and revert to this
> default. ]]
>
> The symbol chosen would depend on the magnitude of the value being
> displayed, and the number of significant figures desired.
>
> SI has little guidance on how to order symbols in a complex quantity,
other
> than there should be only one solidus ('/'), and there should be a minimum
> number of prefixes ("1.0 MN m" not "1.0 kN km"). The fewest prefixes can
be
> obtained by binding to the lowest power-magnitude symbols first, although
> the mechanism should continue to try other prefixes on the rest of the
> symbols, to support other unit systems.
>
> Note that while SI used the magnitude prefixes ('n', 'm', 'k', 'M'...) in
> combination with base symbols, other systems may use different symbols for
> different magnitudes ("thou" or "mil", "in", "ft", "yd", "mi").
>
> LOCALES: While the SI symbols are international, the symbol names are
not.
> If we want to support long-form display, "1.0 metre per second", then the
> symbol names should support localisation (anybody got experience with the
> standard lib message catalogue system?).
>
> WIDE STREAMS: Some SI symbols use non-ascii characters, micro and Omega
for
> instance. Non-SI symbols, and localised symbol names, likewise.
>
> I have a reasonable handle on how to implement most of the above. What
this
> does not support are:
> o Units which are not linear scalings of the internal/SI
> representation. For example, celsius or fahrenheit scales.
I'm handling this part by having an extra template parameter in the unit
(apart from the SI base unit exponents), which includes static members
giving offset and multiplier. This allows things like:
celcius c(10);
kelvin k=c;
cout << k; // Prints "283.15 K" (10 °C)
It stores all values internally as SI values, as you say. This only makes a
difference with initialisation and reading/printing the value.
The same mechanism may be used for differentiating between Hertz and
Becquerel, that you mention. The "unit" template, which is used in the full
type, given later in the mail - where also the "set" parameter is
explained - is:
template
class unit;
typedef unit<0,0,-1,0,0,0,0,mul_offset::unit,0> hertz; // "unit" is a
"default"
typedef unit<0,0,-1,0,0,0,0,mul_offset::becquerel,0> becquerel;
typedef unit<0,0,0,0,1,0,0,mul_offset::unit,0> kelvin;
typedef unit<0,0,0,0,1,0,0,mul_offset::celcius,0> celcius;
etc.
"mul_offset" is the namespace for this extra type.
> o Display of non-decimal fractional systems:
> 36° 15' 10" = 632.73 mrad
> 3 mi 50 yd 2 ft 7 in
> 12 hr 16 min 7 s
This is a hard problem. I didn't think of this, before I read up on these
postings, now.
So far, the system handles e.g. "hour ..."
(unit<0,0,1,0,0,0,0,mul_offset::hour>), but it will give the answer in
hours, not hours, minutes and seconds.
> ANOTHER METHOD of displaying units, which occured to me as a solution to
the
> cases which the automatic method misses, is to allow convertsion to
> formatting types. For example:
>
> cout << (feet) ( 1.0 * metre ) \\ " 3.048 ft "
> << (celcius) ( 300.0 * kelvin ) \\ " 26.84 °C "
> << (fahrenheit) ( 300.0 * kelvin ) \\ " 80.312 °F "
> << (degrees) ( 1.0 * radian ) \\ " 57° 17' 44.8" "
This is the one I've been using, by defining conversion operators taking
into account the "mul_offset" parameter. As all units are stored as SI
internally, this only makes a difference for the type of the quantity value,
though, not its internal value. This also means you can use different
measurement systems in the same expression, without overhead, as all
calculations are in terms of SI.
However, this does has the effect, in my current system, that any
computation results in an SI unit, not including the units of the operands
(in the discussion here, this corresponds to a state-based, not
history-based system). For example:
cout << 10.0 * celcius / (1.0 * metre); // Prints "283.15 K/m" (not 10 °C/m)
If you define a unit like this, you may convert the result to it, though:
square_miles area=100 * metre * 100 * metre; // Ok
celcius_per_metre value=10.0 * celcius / (1.0 * metre); // Ok
cout << (square_miles) 100 * metre * 100 * metre; // Prints "...
miles^2"
As mentioned earlier, this also means, though, that it doesn't enforce the
distinction between units with the same units. For example, in the current
system, you can add hertz and becquerel.
On the other hand, a way to extend the system is to add more dimensions (one
for angle, one for solid angle, etc.), which _will_ be enforced.
It seems we're using different approaches, so I'll look into your code, when
I get a chance, as well as making my own available. Up to now, I've kind of
been implementing it independently, to not be "distracted" by other
approaches, and then, I intended to compare more with the alternative
approaches.
My approach is very simple when it comes to the computations with units. For
example the multiplication operator has the following "obvious" approach:
First, the complete quantity type is defined like this ("dim" being short
for "dimensional". "quantity" is an alternativ):
template
class dim;
Use:
typedef dim,
prefix<3> > kilometre;
Or:
typedef dim kilometre;
Here's the multiplication operator:
template<
class Value,
int kg1,int m1,int s1,int A1,int K1,int mol1,int cd1,class MulOffset1,int
Set1,
int kg2,int m2,int s2,int A2,int K2,int mol2,int cd2,class MulOffset2,int
Set2,
class Prefix1,class Prefix2
>
dim<
Value,
unit,
si::prefix::unit
>
operator*(const
dim,Prefix1> &v1,
const
dim,Prefix2> &v2)
{
return dim<
Value,
unit,
si::prefix::unit
>(v1.value()*v2.value());
}
"Set" refers to the handling of different ways of printing the same quantity
(note, not like "becquerel" and "hertz", which aren't really the same
quantity. They are just treated that way by the system in computations (the
MulOffset difference is ignored, as shown above. The result also doesn't
have any stored prefix)), for example:
cout << 1.0 * joule;
This may print "1.0 J", "1.0 N * m" or "1.0 kg * m / s^2", depending on
which (unit) "set" one choose to display. The "history" way of dealing with
this hadn't really occurred to me. However, even with the "history"
approach, you might want to display the result in terms of the base units,
as shown with the last example.
I'm also wondering how or if a "history" approach would differentiate
between the quantities N * m (moment of force) and J (energy), in the result
of a computation.
> Relatively easy to implement, provides another point of dimension checking
> (can only construct a 'feet' from a length quantity).
>
> Reads well if we use plural forms for the conversion (though as shown
above,
> there is a problem where some symbols don't have good plurals;
> fahrenheits?).
I think singular form will be fine. According to the SI specification at the
NIST site, the SI units are supposed to be given as singular, anyway.
Regards,
Terje
------------------------------
Date: Thu, 15 May 2003 00:48:11 +0200
From: =?iso-8859-1?Q?Terje_Sletteb=F8?=
Subject: Re: SI Units Project: [Fwd: ACCU SI -- printing]
>From: "Watts, Simon (UK)"
Quoting a lot, to have something to refer to (also for any replies to this
posting):
> > From: Pete and Lana Klier [mailto:klierz@si-units]
>
> > From what I am gathering about possible designs for printing the units,
we
> are
> > essentially considering 2 algorithms, and possibly a blend of them. I
> would
> > call these "history" based and "state" based. But I'm not certain where
> exactly
> > we are drawing the line between them, so we should probably have some
kind
> of
> > pseudocode algorithm to see where we are going. (apologies for American
> spelling)
>
> I think that the system I have been considering is somewhere in between
> these two. I do keep the composition history as a binary tree in the
> template dimension trait of a unit, such that (abreviating heavily):
>
> U * U => U>
> U / U => U> // '1/D' provided by a typedef in D
>
> With a few leaf dimensions (for dimensionless, and the SI basis
dimensions).
>
> Although I suspect that any implementation would have to end up being a
> hybred method in practice, apart from explicit display types:
> cout << (newton_metres) (4.0 * kilogram * metre * metre / (second *
> second ))
> << endl;
> > ---------- history-based system ----------
> >
> > cout << 4.0 * kilogram * meter * meter / (second * second) << endl;
> > cout << 4.0 * newton * meter << endl;
> > would print
> > 4.0 kilogram meters^2 per second^2
> > 4.0 newton meters
> > exactly the way the units had been input.
>
> This could only be done based on the type of the argument. In the first
> case, this is (M=mass, L=lebgth, T=time):
> U * U * U / ( U * U )
> => U> * U / U>
> => U>,L>> / U>
> => U< D>,L>,D> >
>
> And the second case (F=force):
> U * U
> => U>
>
> Clearly, the two unit types here are distinct and unrelated. However, the
> force dimesion will have been defined in terms of the basis dimensions,
for
> example, mass times acceleration, where accel is velocity / time, and
> velocity is length/time:
>
> F => D,1/T>>
>
> If the first example was reordered such that the resultant type was:
> U< D< D,1/T>>, L>>
> then it is indistinguishable from the second, and will produce the same
> results.
>
> OK, in this case the expression would have to be:
>
> cout << 4.0 * kilogram * metre / second / second * metre << endl;
>
> However, whatever ordering is used, given the large number of possible
> derived types in play, there is a chance that some sub-tree of the history
> will match an existing named dimension/unit and produce an 'unexpected'
> representation.
>
> Furthermore, the unexpected representation may also be misleading, as some
> unit types do not have distinct dimensions, but can be distinguished by
> unique composition histories. My usual examples are
>
> DIMENSIONLESS DIMENSIONS:
> dimensionless_d (fundamental) == 0
> angle_d => D (radians)
> solid_angle_d => D (A==D) (steradians)
>
> INVERSE TIME DIMENSIONS
> frequency_d => D<1/T,0> (hertz)
> activity_d => D<0,1/T> (becquerels)
>
> (I will have to change the definitions of frequency_d and activity_d,
since
> I thing that we should be able to say D == D**).
>
>
> The history system would require that, given a dimension D, we can
> either locate a corresponding Symbol (matching either D _or_ D****),
> and add it to the list to be displayed, or recurse into A and B if no
match
> is found.
> > ----------------- state-based system ----------------
> >
> > In this system, all we care about are the units, *not* what the user
> > has typed in. Thus,
> > 1/sec would always print as "hertz" and kg-m^2 / sec^2 might always
> > print as Newton-meters.
>
> But what about 1/sec also being "becquerel"?... Same dimension, different
> meaning:
>
> Activity a = 10.0 * becquerel; // Discrete events, Dim: 1/T
> Frequency f = 100.0 * hertz; // Periodicity, Dim: 1/T
> cout << a << "\n" << f << "\n" << a/f << endl;
>
> Want this to give:
> 10.0 Bq
> 100.0 Hz
> 0.1 Bq / Hz
>
> Similarly with planar angles and solid angles, both dimensionless
> quantities.
I was thinking about something like this, too. However, does your system
also enforce their distinction, i.e. that you can't add radian and
steradian, or hertz and becquerel? If so, how, if the unit vector is the
same?
If it doesn't, then maybe it should enforce it, at least be possible to
extend with more dimensions, which then would enforce it. There's been
discussion of a quantity library at the Boost list, and one person's
real-world experience with such a system (their home-grown system), and
their domain was robotics, was that they really wanted to be able to
distinguish angle from dimensionless quantities. Thus, in their own system,
angle was an extra dimension.
I didn't follow this route, of storing the history of the computation.
Actually, I didn't think of exactly that way. What I did, about a year ago,
was to store the units used in two typelists, one for the numerator, and one
for the denominator. Thus, you could represent joule = kg * m / s^2 like
this:
unit
Computation of quantities, at least for division, involved some elaborate
typelist computation, merging typelists, and eliminating equal elements in
the numerator and denominator. Multiplication could be done merely by
merging the respective numerators and denominators.
Template specialisations were used to give names to the units. So the
specialisation of the above unit-template would give two other typelists
(only one, the numerator is used in this case), representing the displayed
name: TYPELIST_1(J) (joule) or TYPELIST_2(N, m), depending on the "set"
used.
An advantage of this system is that it supported any number and any
dimensions. You could for example easily support angle: TYPELIST_1(rad).
Each of these elements in the typelists were unique types, so TYPELIST_1(Hz)
was _not_ the same as TYPELIST_1(Bq).
However, I then thought, what if you e.g. multiply hertz with seconds? It
would give TYPELIST_2(Hz, s), not a dimensionless quantity. If instead hertz
was represented as unit (1/s), then the result
would be dimensionless, as expected. It would also mean that you could no
longer distinguish hertz from becquerel, if becquerel was represented the
same way.
Apparently, there's a problem with defining hertz and becquerel, using the
history-based approach described in the above posting, so what seems like an
arbitrary distinction, D<1/T, 0> and D<0, 1/T> is chosen (correct me if I'm
wrong). Therefore, I'll look and angle, instead, here:
A=D // angle
If we compute A/T we get D // angular velocity
However, if we have some intermediate computation, say A*L/(T * L), we'd get
an entirely different expression tree, but it should represent the same.
Would it be possible to get the same representation, or would that be in the
"tricky"-category you mentioned in another posting?
This also reminds me of the similar case of interpreting TYPELIST_2(Hz, s)
as dimensionless.
Regards,
Terje
------------------------------
Date: Thu, 15 May 2003 10:35:54 +0100
From: "Watts, Simon (UK)"
Subject: RE: SI Units Project: [Fwd: ACCU SI -- printing]
> From: Terje Slettebų [mailto:tslettebo@si-units]
> I was thinking about something like this, too. However, does your system
> also enforce their distinction, i.e. that you can't add radian and
> steradian, or hertz and becquerel? If so, how, if the unit vector is the
> same?
As I mention in another reply, operations between quantities are vetted on
dimension analysis only, not on history. So Hz+Bq, rad+srad, Hz=Bq etc get
through.
> If it doesn't, then maybe it should enforce it, at least be possible to
> extend with more dimensions, which then would enforce it. There's been
Yes I agree. I am thinking along the lines of a distinct dimensionless
type, 'dimensionless_d', as a building block of higher order
dimensions, and adding some vetting into the dim.analysis to prevent
additional of units with different dimensionless_d's.
This is non-trivial (but possible), and they could still be multiplied, and
do we prevent conversion?
There are cases where you want to get an angle from the division of two
lengths though, so: 'Angle = Length / Length' has to be permitted.
> Apparently, there's a problem with defining hertz and becquerel, using the
> history-based approach described in the above posting, so what seems like
an
> arbitrary distinction, D<1/T, 0> and D<0, 1/T> is chosen (correct me if
I'm
> wrong). Therefore, I'll look and angle, instead, here:
>
> A=D // angle
>
> If we compute A/T we get D // angular velocity
>
> However, if we have some intermediate computation, say A*L/(T * L), we'd
get
> an entirely different expression tree, but it should represent the same.
> Would it be possible to get the same representation, or would that be in
the
> "tricky"-category you mentioned in another posting?
There is a distinction between the history and the dimension-vector. While
'A/T' and 'A*L/(T*L)' have different histories, they have the same
dimension-vector. The history is present as an aid to distinguishing units
with the same dimensionality, but operations and conversions refer to the
dimension vector:
A/T -> history: (A,1/T), dimension: (T^-1)
A*L/(T*L) -> history: ((A,L),1/(T,L)), dimension: (T^-1)
Si.
------------------------------
Date: Thu, 15 May 2003 10:14:35 +0100
From: "Watts, Simon (UK)"
Subject: RE: SI Units Project: printing units?
So, there is life in this list still....!
> From: Terje Slettebų [mailto:tslettebo@si-units]
> > (Hertz and Bequeral are both time^{-1)).
>
> And this. However, in my system, the difference isn't enforced, so it _is_
> be possible to add hertz and becquerel, even though it wouldn't make much
> sense. As I understand, your system treats them as distinct, and hence
> prevents what's probably an error.
Unfortunately not; operations are vetted based on dimensional analysis, so
Hz and Bq are allowed to add and be assigned. The history approach was
aimed at distinguishing the units for display: Hz / Bz -> dimensionless,
but could be printed with units "Hz / Bq" for instance.
I have been mulling adding a system for distinct dimensionless dimensions:
dimensionless_d, which would be used in the composition of
higher dimension types, and then could be considered on
assignment/subtraction:
typedef dimensionless_d<1000> apple_d;
typedef dimensionless_d<1001> pear_d;
then disallow adding apples and pears.
(not thought this through completely yet though).
> > o Units which are not linear scalings of the internal/SI
> > representation. For example, celsius or fahrenheit scales.
>
> I'm handling this part by having an extra template parameter in the unit
> (apart from the SI base unit exponents), which includes static members
> giving offset and multiplier. This allows things like:
>
> celcius c(10);
>
> kelvin k=c;
>
> cout << k; // Prints "283.15 K" (10 °C)
>
> It stores all values internally as SI values, as you say. This only makes
a
> difference with initialisation and reading/printing the value.
This is an good idea, although not appropriate for my system it does spark a
possible solution for me.
I use a different terminology/semantic(?) approach however:
Unit [1] types are based on the quantity being measured, not the units of
measurement. Thus, I have a type 'Temperature', not 'celcius' or 'kelvin'
etc. As always, the internal representation is in SI.
'kelvin' is then a constant of type 'Temperature' and value 1.0 kelvin.
Expressions are then:
Temperature t1 = 300.0 * kelvin;
where the operation 'double * Temperature -> Temperature' is supported, as
is explicit construction from a 'double' (for underlying 'double'
representation).
Personally, I find this more in keeping with
Now, I think I could define a constant 'celcius' to be an instance of a
different, but related type, 'Convertor', a unary functor on a
double returning a Temperature, and overload 'double *
Convertor --> Temperature' by applying the function. The
function being defined by parameters on construction of 'celcius'.
This would allow more complex relationships between non-SI and SI scales to
be modelled, if the functor is constructed with polynomial coefficients:
Convertor const celcius (283.15, 1.0 /*...*/);
// x^0 x^1 ...x^n
The Convertor instances would have to support usage as other unit types:
300.0 * metre / celcius
Which may be sufficient that they convert to a unit-value in SI. Whether
this is meaningful in the case of, hypothetical polynomial relationships is
dubious though!
[1] Suggests that Unit is the wrong collective name for these types --
Quantity?
> > cout << (feet) ( 1.0 * metre ) \\ " 3.048 ft "
> > << (celcius) ( 300.0 * kelvin ) \\ " 26.84 °C "
> > << (fahrenheit) ( 300.0 * kelvin ) \\ " 80.312 °F "
> > << (degrees) ( 1.0 * radian ) \\ " 57° 17' 44.8" "
> > ...
> > Reads well if we use plural forms for the conversion (though as shown
above,
> > there is a problem where some symbols don't have good plurals;
> > fahrenheits?).
>
> I think singular form will be fine. According to the SI specification at
the
> NIST site, the SI units are supposed to be given as singular, anyway.
Ah, but these the types 'feet', 'celcius', 'farhrenheit' in *this* case
don't represent unit/quantity classes, but purely temporary formatting
objects, and therefore need to have names distinct from the unit value
instances (or convertors described above). We may already have a const
value called 'foot' in the namespace for instance. They would be something
like:
class foot
{
public:
explict foot (Length const&);
ostream& operator<< (ostream&);
};
If you are going to be looking at my code, remember that the URL on the proj
homepage is out of date. As posted previously, I am now using:
Source zip archive:
http://homepage.ntlworld.com/sawatts/projects/units/units.zip
Documentation top:
http://homepage.ntlworld.com/sawatts/projects/units/index.html
Si.
------------------------------
Date: Thu, 15 May 2003 15:30:17 +0200
From: =?iso-8859-1?Q?Terje_Sletteb=F8?=
Subject: Re: SI Units Project: printing units?
>From: "Watts, Simon (UK)"
> So, there is life in this list still....!
Yup. :)
> > > (Hertz and Bequeral are both time^{-1)).
> >
> > And this. However, in my system, the difference isn't enforced, so it
_is_
> > possible to add hertz and becquerel, even though it wouldn't make much
> > sense. As I understand, your system treats them as distinct, and hence
> > prevents what's probably an error.
>
> Unfortunately not; operations are vetted based on dimensional analysis, so
> Hz and Bq are allowed to add and be assigned. The history approach was
> aimed at distinguishing the units for display: Hz / Bz -> dimensionless,
> but could be printed with units "Hz / Bq" for instance.
However, what would something like Hz * s, which is also dimensionless,
give?
A user may want this to appear as dimensionless (i.e. resulting in no
symbols). Another example may make it clearer:
Hz * s * m // Display as "Hz * s * m" or "m"?
It was issues like this, when or if to collapse the history to the resulting
unit, that I found hard, with this approach.
> I have been mulling adding a system for distinct dimensionless dimensions:
> dimensionless_d, which would be used in the composition of
> higher dimension types, and then could be considered on
> assignment/subtraction:
>
> typedef dimensionless_d<1000> apple_d;
> typedef dimensionless_d<1001> pear_d;
>
> then disallow adding apples and pears.
One possible problem with using arbitrary integers to distinguish them is a
possible collision with units defined in different places, using the same
values. For example apple_d may use 1000 in one file, and pear_d may use
1000 in another file.
Another possibility is to use the type system to guarantee uniqueness, so
you could have, e.g:
class apple_d {};
class pear_d {};
Now, they are guaranteed not to clash.
One thing that also came up on the Boost list (someone submitted a simple
quantity library, but it didn't model the SI system, and was not meant for
that), was the possibility to define the result type different from the
arguments, e.g. "fruits=apples+pears;" ;)
> > > o Units which are not linear scalings of the internal/SI
> > > representation. For example, celsius or fahrenheit scales.
> >
> > I'm handling this part by having an extra template parameter in the unit
> > (apart from the SI base unit exponents), which includes static members
> > giving offset and multiplier. This allows things like:
> >
> > celcius c(10);
> >
> > kelvin k=c;
> >
> > cout << k; // Prints "283.15 K" (10 °C)
> >
> > It stores all values internally as SI values, as you say. This only
makes
> a
> > difference with initialisation and reading/printing the value.
>
> This is an good idea, although not appropriate for my system it does spark
a
> possible solution for me.
>
> I use a different terminology/semantic(?) approach however:
>
> Unit [1] types are based on the quantity being measured, not the units of
> measurement. Thus, I have a type 'Temperature', not 'celcius' or 'kelvin'
> etc. As always, the internal representation is in SI.
>
> 'kelvin' is then a constant of type 'Temperature' and value 1.0 kelvin.
> Expressions are then:
> Temperature t1 = 300.0 * kelvin;
Right. I do actually have "temperature" as an alternative way of specifying
the quantity. Yes, I've seen that the names I've used is slightly different
from the common one. I agree that the quantity is "temperature", and the
unit is "kelvin".
Thus, my system could also do "Temperature t1 = 300.0 * kelvin", where
"kelvin" is a constant of type Temperature, like you write, here. However,
as I've also been experimenting with storing the unit as part of the
quantity type, this means one can do ("unit<...>", below may not be a good
name for the integer vector, after all, since it really describes a
quantity):
quantity > // This is the
quantity temperature, using kelvin
quantity > // This is the
quantity temperature, using celcius
These quantities may then be provided as convenience templates, meaning the
same as the above:
kelvin
celcius
This may make it easier to specify, and print out, quantities, using
different units:
celcius t(10.0);
This is the same as:
temperature t(283.15); // Implicit kelvin
temperature t=283.15 * kelvin; // Copy constructor used
temperature t=10.0 * celcius; // Ditto
However, it may instead be desired to explicitly provide the unit to use:
temperature t=10.0 * celcius;
and to disallow construction with a number.
A possible advantage with the above system was the mentioned ease of
specifying and printing. For example:
celcius t(10.0);
cout << t; // Prints "10 °C" (not 283.15 K)
The value is still stored internally as SI, though (kelvin).
The same goes for "feet", "kilometre", "light_year", etc. The type will know
how to convert from/to its internal representation, and no manipulators,
etc. is needed. If another representation is desired, then similar to the
use of manipulator like you showed, a conversion may be used:
cout << (metre) feet(1.0); // Prints "... m" (both "metre"
and "feet" are instance of the "quantity" template)
Again, it may be better to have only "length", and use manipulators to
select unit, as in your approach. We're exploring alternatives, here. :)
> where the operation 'double * Temperature -> Temperature' is supported, as
> is explicit construction from a 'double' (for underlying 'double'
> representation).
Ok. Do you mean in the latter case:
Temperature t(10.0); // Implicitly kelvin (like I have above, as well)
Note that the following is essentially the same (but requires an accessible
copy constructor):
Temperature t=10.0;
Both use a constructor taking a double.
> Now, I think I could define a constant 'celcius' to be an instance of a
> different, but related type, 'Convertor', a unary functor on
a
> double returning a Temperature, and overload 'double *
> Convertor --> Temperature' by applying the function. The
> function being defined by parameters on construction of 'celcius'.
>
> This would allow more complex relationships between non-SI and SI scales
to
> be modelled, if the functor is constructed with polynomial coefficients:
>
> Convertor const celcius (283.15, 1.0 /*...*/);
> // x^0 x^1 ...x^n
>
> The Convertor instances would have to support usage as other unit types:
>
> 300.0 * metre / celcius
>
> Which may be sufficient that they convert to a unit-value in SI. Whether
> this is meaningful in the case of, hypothetical polynomial relationships
is
> dubious though!
Might a similar approach be able to model things like hour/min/sec, as one
unit?
It seems we agree that internal storage using SI appears to give the easiest
manipulation, and no run-time overhead, since only SI-units are used.
Then, the question is I/O. Perhaps one could either use a manipulator, or a
quantity with a given unit (like the quantity<> above) then perform proper
formatting for this case. The conversion would then be seconds <--->
hour/min/sec.
Anyway, this appears to be a pretty hard problem (like how to provide
customisation of the formatting in this case), and it may be more important
to get the base functionality in place.
> [1] Suggests that Unit is the wrong collective name for these types --
> Quantity?
It appears that "quantity" is more common, with regard to such libraries,
yes. As you point out (and also stated at e.g. the NIST site), the quantity
is e.g. "temperature", and the unit is "kelvin", "celcius", etc.
So, as mentioned earlier, in my example, an integer vector (template with
integer parameters) taking the dimensions may be better named as
quantity.
> > > cout << (feet) ( 1.0 * metre ) \\ " 3.048 ft "
> > > << (celcius) ( 300.0 * kelvin ) \\ " 26.84 °C "
> > > << (fahrenheit) ( 300.0 * kelvin ) \\ " 80.312 °F "
> > > << (degrees) ( 1.0 * radian ) \\ " 57° 17' 44.8" "
> > > ...
> > > Reads well if we use plural forms for the conversion (though as shown
> above,
> > > there is a problem where some symbols don't have good plurals;
> > > fahrenheits?).
> >
> > I think singular form will be fine. According to the SI specification at
> the
> > NIST site, the SI units are supposed to be given as singular, anyway.
>
> Ah, but these the types 'feet', 'celcius', 'farhrenheit' in *this* case
> don't represent unit/quantity classes, but purely temporary formatting
> objects, and therefore need to have names distinct from the unit value
> instances (or convertors described above). We may already have a const
> value called 'foot' in the namespace for instance. They would be
something
> like:
>
> class foot
> {
> public:
> explict foot (Length const&);
> ostream& operator<< (ostream&);
> };
Right. So would this class behave like a quantity, i.e. be usable like
"length", etc. is?
In that case, it's similar to my use of "celcius", "kelvin", etc., above.
Regards,
Terje
------------------------------
Date: Thu, 15 May 2003 15:42:36 +0200
From: =?iso-8859-1?Q?Terje_Sletteb=F8?=
Subject: Re: SI Units Project: [Fwd: ACCU SI -- printing]
>From: "Watts, Simon (UK)"
> > If it doesn't, then maybe it should enforce it, at least be possible to
> > extend with more dimensions, which then would enforce it.
>
> Yes I agree. I am thinking along the lines of a distinct dimensionless
> type, 'dimensionless_d', as a building block of higher order
> dimensions, and adding some vetting into the dim.analysis to prevent
> additional of units with different dimensionless_d's.
>
> This is non-trivial (but possible), and they could still be multiplied,
and
> do we prevent conversion?
Well, if angle is considered a real dimension in the given domain, then it
wouldn't allow conversion. On the other hand, maybe this is too strict,
given that the SI unit vector is the same.
> There are cases where you want to get an angle from the division of two
> lengths though, so: 'Angle = Length / Length' has to be permitted.
Right. Tricky. Here - unless the history is used for dimensional analysis -
it has to allow conversion from dimensionless to angle.
> > A=D // angle
> >
> > If we compute A/T we get D // angular velocity
> >
> > However, if we have some intermediate computation, say A*L/(T * L), we'd
> get
> > an entirely different expression tree, but it should represent the same.
> > Would it be possible to get the same representation, or would that be in
> the
> > "tricky"-category you mentioned in another posting?
>
> There is a distinction between the history and the dimension-vector.
While
> 'A/T' and 'A*L/(T*L)' have different histories, they have the same
> dimension-vector. The history is present as an aid to distinguishing
units
> with the same dimensionality, but operations and conversions refer to the
> dimension vector:
>
> A/T -> history: (A,1/T), dimension: (T^-1)
> A*L/(T*L) -> history: ((A,L),1/(T,L)), dimension: (T^-1)
Yes, sorry, I meant "represented", as in how it's displayed, not its
internal representation (which, at least regarding the unit vector, would be
the same for both, as you say). As I understood, the history may aid in
display, which is why I wondered how or if it would be able to cope with a
situation like this.
Regards,
Terje
------------------------------
End of si-units-digest V1 #17
*****************************
**