From: si-units-list@si-units (si-units-digest)
To: si-units-digest@si-units
Subject: si-units-digest V1 #18
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 Wednesday, May 21 2003 Volume 01 : Number 018
In this issue:
RE: SI Units Project: printing units?
Re: SI Units Project: printing units?
RE: SI Units Project: printing units?
Re: SI Units Project: printing units?
RE: SI Units Project: printing units?
Re: SI Units Project: printing units?
RE: SI Units Project: printing units?
Re: SI Units Project: printing units?
RE: SI Units Project: printing units?
Re: SI Units Project: printing units?
----------------------------------------------------------------------
Date: Thu, 15 May 2003 18:25:56 +0100
From: "Watts, Simon (UK)"
Subject: RE: SI Units Project: printing units?
> From: Terje Slettebų [mailto:tslettebo@si-units]
> However, what would something like Hz * s, which is also
> dimensionless,
> give?
Right, this should give dimensionless.
I would see the history approach as complementary to the immediate state,
and used in conjunction with a table of symbols (data/types containing
magnitude, dimension, textual, and formatting information).
The history consists of a binary tree of dimension traits; we will do a
breadth-first search of this tree, terminating any branch when the dimension
trait is present in the symbols table. Sum the symbol occurances at the
termination points: +1 for 'D', -1 for '1/D' (inverse or reciprical).
Discard any symbols with a net count of zero.
So, for the case 'Hz * s * m', the tree looks like:
?
/ \
/ \
? m
/ \
/ \
Hz s
/ \
/ \
0 1/s
where '?' represents a dimension trait not in the (current) table. ('Hz'
breaks down further, but the search stops before there).
The breadth-first search returns the following symbols and counts: ( m=1 ,
Hz=1, s=1 )
Next comes the dimensional analysis part, to determine the power term of the
symbol: 'Hz * s * m' has dimensionality ( L^1 ). For dimensionless symbols,
just use the count from the tree search. For others, divide out the
symbol's dimensionality from that of the unit:
m: ( L^1 ) --> m^1, remainder ( 0 )
Hz: ( T^-1 ) --> Hz^0, remainder ( 0 )
s: ( T^1 ) --> s^0, remainder ( 0 )
[TODO: Effect of the ordering of the symbols in this process ]
Now filter out all symbols with zero-power, to leave the compound symbol: (
m^1 )
We can determine the magnitude represented by the compound symbol as the
power-product of the magnitudes represented by each part, which is used to
divide the quantity being formatted.
SI Prefixes (and scale-dependant symbol changes) can be applied as well now.
> > typedef dimensionless_d<1000> apple_d;
> > typedef dimensionless_d<1001> pear_d;
> 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.
Quite correct. Though since there is legwork in the dimensionless_d<>
template, the following may be preferable:
struct apple_tag {};
struct pear_tag {};
typedef dimensionless_d apple_d;
typedef dimensionless_d pear_d;
> 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;" ;)
template
struct dimensionless_d
{
typedef TAG tag_type;
// ...
};
struct fruit_tag {};
struct apple_tag : public fruit_tag {};
struct pear_tag : public fruit_tag {};
typedef dimensionless_d fruit_d;
typedef dimensionless_d apple_d;
typedef dimensionless_d pear_d;
And then some dispatching compatible tag_types?
> ...
>
> 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.
This is the method I prefer -- it reads more like a technical/scientific
paper: "t0 = 10.0°C, t2 = 39.8°C". You wouldnt say "°C = 10.0".
> > 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.
The former. I have defined ctor-from-datatype as explicit. The intention
is that 'Temperature t(10.0);' can be used to construct the bases quantities
(kelvin), but the user would be writing 'Temperature t = 10.0 * kelvin;'.
However, thinking about the 'Convertor' thing, it may be better
to define all constant basis quantities with those types.
> > Convertor const celcius (283.15, 1.0 /*...*/);
> > // x^0 x^1 ...x^n
> Might a similar approach be able to model things like
> hour/min/sec, as one
> unit?
Hm, not sure if it is the right place. I think this is better done as part
of the symbol formatting -- where we have to deal with prefixes and
scale-dependant-symbols (mm,m,km, in, ft, yd etc). Such series of symbols,
for difference magnitudes, should be held within a single 'symbol' instance.
I think that the multi-fractional representations (making up the term!)
would only be applicable when the display resolves to a single symbol with
+1 power.
> 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.
YES YES YES YES YES!!!! :-)
A solid internal representation not compromised for external HCI! (I have
had that conversation a number of times...)
> > cout << (feet) (1.234 * metre) << endl;
// CORRECTED: foot --> feet
> > class feet
> > {
> > public:
> > feet (Length const&); // explicit??
> > 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.
No, this should be treated purely as a formatting tool, the same as saying
"print 1.234m in feet". I dont see them having any existance beyond the
inline use in streaming.
More a form of stream manipulator that takes a quantity in construction:
cout << feet (1.243*metre)
Si.
------------------------------
Date: Thu, 15 May 2003 23:58:32 +0200
From: =?iso-8859-1?Q?Terje_Sletteb=F8?=
Subject: Re: SI Units Project: printing units?
>From: "Watts, Simon (UK)"
I'll get back to your last posting, I just would like to bring up another
point, as well: The handling of prefixes.
I went into some about this, with the earlier posting about the special
case, "kg". However, it turns out that even without this, it's more complex
than I had thought of.
My way of handling prefixes has been to encode them as part of the quantity,
so you could e.g. have a "kilometre" quantity, instead of "length l=... *
kilo * metre". However, as you point out in the previous posting, this
latter way may be preferable, after all.
Another way is to use "dynamic prefix", having the prefix be calculated when
printing it. In that case, no specified prefix is used.
In any case, if it's specified, then no matter how it's specified, as part
of the type or as part of the values used, there's a question how to use it.
An example of having it as part of the type may show the issues involved.
Let's use a simplified way of specifying it:
quantity km(1.23);
cout << km; // Prints "1.23 km"
However, it turned out that this was way too simplistic way of dealing with
it. What I (re-)realised, when reading the SI specifications, again, was
that the prefix and the unit it applies to is treated as a single entity.
Consider (names rather than lists of integers used):
quantity km(1);
1 km^2 = (1000 m)^2 = 10^6 m^2
This is fine, we could handle this conversion to metre. What I then found,
was, what if "metre_squared" has another name (like many units have other
names, such as "newton" for "kg * m * / s^2"). If you then substitute one
for the other, not taking the prefix into account, it becomes wrong, as the
meaning of the prefix depends on the exponent of the unit next to it. If we
sustitute "m^2" with "area" (as a made-up unit for this), and therefore
print it as "1 kiloarea", it becomes wrong, as this translates back to 1000
* m^2 = 10^3 m^2.
Therefore, the prefix to print needs to take into consideration the unit
which it is placed next to. This means that the approach of one prefix per
quantity doesn't work. An alternative is not storing the prefix, at all, or
storing a prefix for each element in the quantity, and then transforming the
expression, merging more or less all of them (an exception might be "kg"),
displaying it at the front of the expression.
I've looked at your docs, but they appear not to handle the issue of
prefixes. Have you considered how to do it?
I can think of a couple of approaches:
1. Not storing the prefix in the quantity, and instead either not using
prefix when displaying, or using an algorithm to compute the best way of
displaying it. This latter part may be tricky, due to especially how to
handle "kg", as detailed in an earlier posting.
2. Storing the prefix for each element in the unit, and use this to provide
the formatting. For example, if someone would like to use the unit "mg/ml"
(which is g/l = 10^-3 kg / (10^-1 m) ^3 = kg / m^3), they might use:
element:
typelist, element > // 10^-3 kg * 10^3 m^-3 =
mg/ml
If this is encoded in the type, it could be printed as "mg/ml", or whatever
unit the user would like to use. This leaves the formatting up to the user.
It also takes care of how units involving "kg" should be printed, e.g 10^6
m/kg^2 - as "1 Mm/kg^2", or "1 km/g^2", or what.
This is a completely new approach from my current one, as I realised today
that the one stored prefix per quantity doesn't hold, as it depends on how
the unit is interpreted.
Anyway, just some thinking out loud. :)
Yes, I also appreciate having this list, and people here to talk with,
especially if one is banging one's head against the wall because of
something. :)
Regards,
Terje
------------------------------
Date: Fri, 16 May 2003 10:03:27 +0100
From: "Watts, Simon (UK)"
Subject: RE: SI Units Project: printing units?
I am finding this discussion very interesting as it is thrashing out a
number of ideas and scenarios for dealing with dimensioned quantities. I
must admit that I had put this project on a back burner for a while now
("work" things etc). This may be sufficient motivation to reinspect my code
this weekend...
PREFIXES
My view is that these are purely part of the printing mechanism, and
shouldnt be attached to the data types. I would aim to keep the user level
API as simple as possible, and as close to that used in technical/scientific
docs as possible (given the requirements of the language).
There is nothing wrong with having constants defined for both 'kilo'
(dimensionless, 1000.0), 'metre' (length, 1.0), and 'kilometre' (length,
1000.0) (etc). So yes, the following would be equal:
Length l1 = 1000.0 * metre;
Length l2 = 1.0 * kilo * metre;
Length l3 = 1.0 * kilometre;
Something which we may (should) consider when printing is the existing
numerical formatting of the output stream (TC++PL 21.4.3 etc). How would
these be honoured?
Anyway, I can handle the 'g' vs 'kg' situation in the model I have been
considering...
Previously I mentioned getting a set of symbols by a breadth-first search of
the history trait, with reference to a symbol table.
Now, these symbol objects need not represent a single symbol (0.001, "g",
"gram"), but a set of symbols for a given dimension: { ... (0.001, "g",
"gram"), (1.0, "kg", "kilogram"), (1000.0, "Mg", "megagram"), ... }.
For SI symbols, the sets are formed by algorithm, applying prefixes to base
names and quantities. So, for Length we have ( 1.0, "m", "metre"), for Mass
we have ( 0.001, "g", "gram"). This does not break the
internal-representation-is-SI for Mass, as Mass (1.0) is still "kg", just
the prefix handling is rebased in this case to account for the anomaly.
So, given a magnitude, we can ask a 'Symbol', or more accurately
'SymbolSet', for the largest symbol less-than the magnitude (or the min/max
limit supported):
[ 1000.0 , "kg", "kilogram" ] = si_mass_symbols.for ( 11234.0 );
| | ^-- symbol name (for locale lookup)
| +-- symbol
+-- magnitude (SI) represented by the symbol.
So we can then scale the magnitude by the symbol magnitude for display:
11.234 kg
Formatting for a number of significant figures, and handling powers of units
(etc), can done by manipulation of the magnitude being matched.
And now for the cunning bit -- the same system can be used in systems where
the units change with magnitudes begin represented, though these may not use
an algorithm like the systematic SI approach. The SymbolSet for Length in
Imperial may be:
{...(0.0254, "in", "inch" ), (0.3048, "ft", "foot"), (0.9144, "yd",
"yard"),...}
A remaining strategy which needs to be decided is the order in which
specific symbols are processed in compound symbol.
Assume that a quantity, q, being printed is determined to be represented by
a compound symbol ( A^2, B, C^-1 ) (where A,B,C are symbol sets present in
the current symbol table).
We obtain a symbol and value for each symbol set in some order, adjusting
the quantity for the selected symbol before moving on to the next. For
example, taking the order given above:
q /= pow ( 10.0, sig); // want to keep sig digits before decimal
point
[ va, "a" ] = A.for ( pow (q, 1.0/2.0) );
q /= pow (va, 2.0);
[ vb, "b" ] = B.for ( pow (q, 1.0/1.0) );
q /= pow (vb, 1.0);
[ vc, "c" ] = C.for ( pow (q, 1.0/-1.0) );
q /= pow (vc, -1.0);
out << q * pow (10.0, sig) << "a" << "b" << "c";
This mechanism should permit multiple 'prefixed', as it also supports non-SI
unit systems. The trick is to order the elements of the compound symbol in
this process such that, for most cases in SI, only a single prefix is
necessary.
Clearly, SI will produce the finest resolution for lowest power terms, BUT I
suspect that this may produce silly results if we always fit to the
large-negative-powers.
My thoughts are that sort by |power|, lowest first (tie: positive first).
Though test cases are required here.
> From: Terje Slettebų [mailto:tslettebo@si-units]
> I've looked at your docs, but they appear not to handle the issue of
> prefixes. Have you considered how to do it?
Unfortunately, I havent put any of these fine ideas into code yet. As I
mentioned above, this flurry of interest may kick me into action though.
(think I started to implement a while ago, but got distracted!)
Si.
------------------------------
Date: Sat, 17 May 2003 10:08:46 +0200
From: =?iso-8859-1?Q?Terje_Sletteb=F8?=
Subject: Re: SI Units Project: printing units?
> PREFIXES
>
> My view is that these are purely part of the printing mechanism, and
> shouldnt be attached to the data types. I would aim to keep the user
level
> API as simple as possible, and as close to that used in
technical/scientific
> docs as possible (given the requirements of the language).
>
> There is nothing wrong with having constants defined for both 'kilo'
> (dimensionless, 1000.0), 'metre' (length, 1.0), and 'kilometre' (length,
> 1000.0) (etc). So yes, the following would be equal:
> Length l1 = 1000.0 * metre;
> Length l2 = 1.0 * kilo * metre;
> Length l3 = 1.0 * kilometre;
>
> Something which we may (should) consider when printing is the existing
> numerical formatting of the output stream (TC++PL 21.4.3 etc). How would
> these be honoured?
Wouldn't that be handled automatically, if you print the value to the given
stream?
As I understand from the explanation that followed in your previous posting,
your approach is that given the above statements:
cout << l1 << '\n' << l2 << '\n' << l3 << '\n;
all would print the same, e.g. "1000 m".
This is because any history information, in the form of type information
from the expression tree, will be collapsed to the type "Length".
However, the following:
cout << Length(1) * kilo * metre;
may print "1 km", as the type information is still available at the point
it's being printed.
Is this understanding correct?
Of course, manipulators might also be used to select the unit/prefix to
print.
Regards,
Terje
------------------------------
Date: Mon, 19 May 2003 09:34:00 +0100
From: "Watts, Simon (UK)"
Subject: RE: SI Units Project: printing units?
> From: Terje Slettebų [mailto:tslettebo@si-units]
> > PREFIXES
> > There is nothing wrong with having constants defined for both 'kilo'
> > (dimensionless, 1000.0), 'metre' (length, 1.0), and
> 'kilometre' (length,
> > 1000.0) (etc). So yes, the following would be equal:
> > Length l1 = 1000.0 * metre;
> > Length l2 = 1.0 * kilo * metre;
> > Length l3 = 1.0 * kilometre;
> >
> > Something which we may (should) consider when printing is
> > the existing
> > numerical formatting of the output stream (TC++PL 21.4.3
> > etc). How would these be honoured?
> Wouldn't that be handled automatically, if you print the
> value to the given
> stream?
Not in conjunction with the prefixes, which take some of the magnitude of
the value. The normal stream formatting for (in this case) floats will
display the final value as normal, yes, but that value is not necessarily
the same as the quantity (in SI) -- it is scaled by the symbol (prefix in
SI, sub-symbol in other systems). Choosing the symbol therefore affects the
precision displayed -- '1234 mm' or '1.234 m'?
[See below]
> As I understand from the explanation that followed in your
> previous posting,
> your approach is that given the above statements:
>
> cout << l1 << '\n' << l2 << '\n' << l3 << '\n;
>
> all would print the same, e.g. "1000 m".
>
> This is because any history information, in the form of type information
> from the expression tree, will be collapsed to the type "Length".
No, they should all show '1.0 km'.
To make a distinction, assume the quantity is 1234 m;
Retrieving the symbol for the quantity would give the 'SI symbol for
length', which is an object on which we can ask 'what is the symbol for a
magnitude of 1234.0'. This returns a specific symbol, 'km', representing a
value of '1000.0'. We then display (1234.0/1000.0) 'km'.
This is also where we have to handle the precision. Since the symbol
absorbs some of the magnitude, we may want to impose some number of digits
before the decimal point (the equivalent of precision in normal float
representation).
For a clearer (?) example, consider '1.0668 m' is to be displayed in
'in/ft/yd' (and we are not using a fractional representation here!). We
could display as '42 in', '3.5 ft', or '1.167 yd'.
If symbols' simply return the largest magnitude symbol less than a given
quantity, this would give '1.167 yd'.
But if the current formatting accepted (N = ) 3 digits (before decimal),
then '42 in' would give the most precise representation.
So the question asked of the symbol(-set) is 'what is the symbol such that
quantity X is scaled to the range [1.0,10^N)'.
Also note that the number of digits before the decimal is different from the
float precision -- number of digits after the decimal. I don't think
existing formatting supports the concept (it would only make sense for
'1.0E10' notation). If it isnt there, then we could add the option.
One other thing that needs to be mentioned here: The formatting assumes
'double' (or float) representation. Since the user may be using other
datatypes (complex, vector etc), then rather than force them to reimplement
the symbol tables, they will have to specialise a template class to allow
our formatting system to extract information in terms of doubles (and apply
appropriate scalings etc).
Si.
PS: Didnt have time to do anything on this over the weekend! Life
sometimes gets in the way...
------------------------------
Date: Mon, 19 May 2003 13:41:51 +0200
From: =?iso-8859-1?Q?Terje_Sletteb=F8?=
Subject: Re: SI Units Project: printing units?
>From: "Watts, Simon (UK)"
> > From: Terje Slettebų [mailto:tslettebo@si-units]
> > > PREFIXES
> > > There is nothing wrong with having constants defined for both 'kilo'
> > > (dimensionless, 1000.0), 'metre' (length, 1.0), and
> > 'kilometre' (length,
> > > 1000.0) (etc). So yes, the following would be equal:
> > > Length l1 = 1000.0 * metre;
> > > Length l2 = 1.0 * kilo * metre;
> > > Length l3 = 1.0 * kilometre;
> > >
> > > Something which we may (should) consider when printing is
> > > the existing
> > > numerical formatting of the output stream (TC++PL 21.4.3
> > > etc). How would these be honoured?
>
> > Wouldn't that be handled automatically, if you print the
> > value to the given stream?
>
> Not in conjunction with the prefixes, which take some of the magnitude of
> the value. The normal stream formatting for (in this case) floats will
> display the final value as normal, yes, but that value is not necessarily
> the same as the quantity (in SI) -- it is scaled by the symbol (prefix in
> SI, sub-symbol in other systems). Choosing the symbol therefore affects
the
> precision displayed -- '1234 mm' or '1.234 m'?
Right.
> > As I understand from the explanation that followed in your
> > previous posting,
> > your approach is that given the above statements:
> >
> > cout << l1 << '\n' << l2 << '\n' << l3 << '\n;
> >
> > all would print the same, e.g. "1000 m".
> >
> > This is because any history information, in the form of type information
> > from the expression tree, will be collapsed to the type "Length".
>
> No, they should all show '1.0 km'.
Ah. Well, it was a bad example. What about angle, after having been assigned
to a quantity variable? Or could the angle/history property be captured in a
variable, as well (run-time information?)?
Regards,
Terje
------------------------------
Date: Mon, 19 May 2003 15:47:56 +0100
From: "Watts, Simon (UK)"
Subject: RE: SI Units Project: printing units?
> From: Terje Slettebų [mailto:tslettebo@si-units]
> Ah. Well, it was a bad example. What about angle, after
> having been assigned to a quantity variable? Or could
> the angle/history property be captured in a variable,
> as well (run-time information?)?
Not quite sure what you are after here.
If the angle has been assigned to an Angle type, then its just another SI
unit and should be identified by the dim.history (compiler time information,
analysed at run time with ref. to a lookup table of symbols).
What may be an issue is where its is not explictly an angle, but happens to
have the same dim.history, where angle is defined as
D
as per SI standard:
Length l1 = 2.0 * metre; // D: L
Length l2 = 1.0 * metre; // D: L
Angle a1 = l1 / l2;
cout << a1 << endl; // gives: "2.0 rad"
cout << l1 / l2 << endl; // also: "2.0 rad"
This is the danger in defining Angle dim history as D, since 'l1/l2'
gives the same history. It is making an assumption about the users intent,
which may be false (in this case, 'l1/l2' may just be a radio).
As you pointed out in a prev post, characterising quantities by composite
dimensions is arbitary (Hz=D<0,1/T>, Bq=D<1/T,0> in that case). At the
time, that was all I had to play with within the system.
The idea of adding distinct dimensionless dimension traits is getting
appealing, as an alternate way to distinguish such same-dimension
quantities.
struct angle_tag {};
typedef dimensionless_tmpl
angle_d;
struct frequency_tag {}
typedef Dimension
<
time_d::rtype,
dimensionless_tmpl
>
frequency_d;
((Where dimensionless_tmpl<> supports the Dimension concept))
This would be an easy implimentation, allow absolute distinction in
printing, but not prevent addition between different types (Hz+Bq).
The harder part would be in disallowing 'Hz+Bq', or 'rad+srad'; or rather,
on complex types where these parts are buried deeper in the composition!
eq;
(m/s) * (bq/m)
vs
(hz*s) / (m*s)
Dimesion history:
( (L,1/T), (BQ, 1/L) )
and
( (HZ, T), 1/(L, T) )
Operations at present begin just determined by the dimesion vector.
Using type lists of dimensions may be a route -- though they would have to
support a number of operations. See 'meta/dimension.h' header from my code,
which has these ops for the simple dim. vector approach.
Si.
------------------------------
Date: Mon, 19 May 2003 17:57:58 +0200
From: =?iso-8859-1?Q?Terje_Sletteb=F8?=
Subject: Re: SI Units Project: printing units?
>From: "Watts, Simon (UK)"
> > From: Terje Slettebų [mailto:tslettebo@si-units]
> > Ah. Well, it was a bad example. What about angle, after
> > having been assigned to a quantity variable? Or could
> > the angle/history property be captured in a variable,
> > as well (run-time information?)?
>
> Not quite sure what you are after here.
>
> If the angle has been assigned to an Angle type, then its just another SI
> unit and should be identified by the dim.history (compiler time
information,
> analysed at run time with ref. to a lookup table of symbols).
How is the Angle type defined, then? Is the history captured in it, as part
of its type? That's what I meant, if Angle captures the history in its type,
or if the history is only available in the expression, not after being
assigned to such a quantity, like Angle. If so, then it should be ok.
> What may be an issue is where its is not explictly an angle, but happens
to
> have the same dim.history, where angle is defined as
> D
> as per SI standard:
>
> Length l1 = 2.0 * metre; // D: L
> Length l2 = 1.0 * metre; // D: L
>
> Angle a1 = l1 / l2;
>
> cout << a1 << endl; // gives: "2.0 rad"
> cout << l1 / l2 << endl; // also: "2.0 rad"
>
> This is the danger in defining Angle dim history as D, since
'l1/l2'
> gives the same history. It is making an assumption about the users
intent,
> which may be false (in this case, 'l1/l2' may just be a radio).
Right.
> As you pointed out in a prev post, characterising quantities by composite
> dimensions is arbitary (Hz=D<0,1/T>, Bq=D<1/T,0> in that case). At the
> time, that was all I had to play with within the system.
>
> The idea of adding distinct dimensionless dimension traits is getting
> appealing, as an alternate way to distinguish such same-dimension
> quantities.
>
> struct angle_tag {};
> typedef dimensionless_tmpl
> angle_d;
>
> struct frequency_tag {}
> typedef Dimension
> <
> time_d::rtype,
> dimensionless_tmpl
> >
> frequency_d;
>
> ((Where dimensionless_tmpl<> supports the Dimension concept))
Yeah.
> This would be an easy implimentation, allow absolute distinction in
> printing, but not prevent addition between different types (Hz+Bq).
Well, that may be ok.
It's an interesting way, using the history, especially if it can be
designated as a type, and I guess it can. There's the issue of if the same
history is used, but the intent was different, as you say.
Regards,
Terje
------------------------------
Date: Tue, 20 May 2003 09:23:30 +0100
From: "Watts, Simon (UK)"
Subject: RE: SI Units Project: printing units?
> From: Terje Slettebų [mailto:tslettebo@si-units]
> How is the Angle type defined, then? Is the history captured
> in it, as part of its type? That's what I meant, if Angle captures the
> history in its type,
> or if the history is only available in the expression, not after being
> assigned to such a quantity, like Angle. If so, then it should be ok.
History is part of the type.
I compose complex dimensions using a 'Dimension' template, such that
a Quantity is defined as 'Unit'. 'Dimension<>', 'D1',
and 'D2' all support the "dimension concept", so this gives the binary tree
of the composition history.
The resultant types for arithmetic operations are quantity types with the
appropriate composition of dimensions: U * U => U ,
etc.
Since composition history is part of the type, assignment to another type
(of the same dimensionality) does not preserve the history. I think this is
right, as the user is placing a specific interpretation on the quantity.
So, 'Acceleration / Velocity' gives '1/Time', which is not a Frequency (Hz)
or Activity (Bq) quantity unless the user allocates to such (or performs a
cast).
We can also take advantage of this to force representations for composite
dimensions:
typedef Dimension angular_momentum_d;
The printing of a quantity based on 'angular_momentum_d' would then be
represented by symbols for force and length (N m).
Now, this does clash with the definition of Energy (force * distance), so
all library-provided types will have to use unique dimensionless tags to
distinguish them from arbitary user compositions:
typedef Dimension energy_d;
Where 'enery_tag' is a third, optional (default = no_tag?) parameter which
is only used to make distinct types with the same composition.
One thing that occurs to me is, just how often will people be streaming out
the results of inlined equations?
cout << length1 * time2 / energy3 << endl;
Si.
------------------------------
Date: Wed, 21 May 2003 07:55:37 -0700
From: Pete and Lana Klier
Subject: Re: SI Units Project: printing units?
I apologize for being so busy. It's encouraging that at least 2 other people
are discussing this. I haven't had a *lot* of time to keep on top of this,
so if there is any duplication, I apologize for that too.
In any case, here is a prototype for the state-based approach that can be
evaluated for its user-friendliness. I apologize for any duplication as I need
to spend more time reading this traffic as well.
It's not fully integrated with the latest
and greatest (volunteers?). Basically it does what I've been threatening to
do: Search a list of type-names (this could be reconfigured) for the closest
match, print it out, subtract it off, repeat. An additional twist is that
*powers*
of types are managed as well (I tried to do some fancy math here, but with
absolute values in the picture, ended up with just blind search from -5..-1,
1..5).
In any case, I'm going to work the issue of user-friendly error-messages; then,
with all printing schemes in place, we can all fully integrate and test the
system.
- -----------------------------------------------------------------------------------
- ---------------- mks.txt -----------------
meters translates to 63.5 meters**1
Newtons translates to 83.7 newtons**1
Newton-meters translates to 5314.95 joules**1
Watts translates to 82.3 watts**1
Seconds translates to 2 seconds**1
Watt-seconds translates to 164.6 joules**1
Watts per second translates to 41.65 watts**1 seconds**-1
ohms translates to 76.3 ohms**1
ohm-meters translates to 111 ohms**1 meters**1
Volts translates to 120 volts**1
volt_meters translate to 42.6 volts**1 meters**1
volts per meter translates to 12 volts**1 meters**-1
ohms per meter translates to 7.63 ohms**1 meters**-1
ohms per meter per second translate to 88.5 farads**-1 meters**-1
Newtons squared translates to 100 newtons**2
Ohms squared translates to 100 ohms**2
Ohms cubed translates to 1000 ohms**3
Ohms cubed per coulomb is 500 ohms**3 coulombs**-1
- ------------ mksname.h --------------------
#ifndef __MKSNAME__
#define __MKSNAME__
#include
class mksnamespace
{
struct _mks_units_list
{
int M;
int K;
int S;
int C;
const char *name;
struct _mks_units_list *next;
_mks_units_list(int M0, int K0, int S0, int C0, const char *name0, struct
_mks_units_list *next0)
: M(M0), K(K0), S(S0), C(C0), name(name0), next(next0) {};
int eval (int N, int Mtest, int Ktest, int Stest, int Ctest) const;
} *mks_units_list,**qend;
const struct _mks_units_list *closest(int M, int K, int S, int C,int *ipow )
const;
public:
mksnamespace (void) : mks_units_list(0),qend(&mks_units_list) {};
void add_entry(int M, int K, int S, int C, const char *name);
template class mksentry
{
private:
mksentry (void);
public:
mksentry (mksnamespace &ns, const char *name) {
ns.add_entry(M,K,S,C,name); };
};
void print_units(ostream &out,int M, int K, int S, int C) const;
ostream & operator >> (ostream &out);
};
ostream & operator << (ostream &out, mksnamespace &ns);
#endif
- --------------- mksname.c++ ---------------------
// -*- compile-command : "g++ -o mks mks.c++ mksname.c++" -*-
#include "mksname.h"
#include
using namespace std;
template T abs (const T &x)
{
if (x < 0) return (- x); else return (x);
}
template T avg (const T &m, const T &k, const T &s, const T &c)
{
return(m+k+s+c)/4;
}
template int round (const T &unrounded)
{
const int ipart = static_cast(unrounded);
T remainder = unrounded - ipart;
if (remainder > 0.5) return ipart + 1;
else if (remainder < 0.5) return ipart;
else if (unrounded < 0) return ipart + 1;
else /*if (unrounded >= 0)*/ return ipart;
}
ostream & operator << (ostream &out, mksnamespace &ns)
{
ns >> out;
return out;
}
int mksnamespace::_mks_units_list::eval (int N, int Mtest, int Ktest, int Stest,
int Ctest) const
{
return abs(N*M - Mtest) + abs(N*K - Ktest) + abs(N*S - Stest) + abs(N*C -
Ctest);
}
void mksnamespace::add_entry(int M, int K, int S, int C, const char *name)
{
*qend = new _mks_units_list(M,K,S,C,name,0);
qend = &((*qend)->next);
}
const struct mksnamespace::_mks_units_list *mksnamespace::closest(int M, int K,
int S, int C,int *ipow) const
{
const struct _mks_units_list *closest_ = mks_units_list;
int best_distance = abs(M) + abs(K) + abs(S) + abs(C) + 1; // the most it
could be
for( const struct _mks_units_list *ptr = mks_units_list;ptr;ptr = ptr->next)
for(int N = 1;N < 6; N++)
{
const int current_distance = ptr->eval(N,M,K,S,C);
const int current_flip_distance = ptr->eval(-N,M,K,S,C);
if (current_distance < best_distance)
{
closest_ = ptr;
best_distance = current_distance;
*ipow = N;
}
else if (current_flip_distance < best_distance)
{
closest_ = ptr;
best_distance = current_flip_distance;
*ipow = -N;
}
}
return closest_;
}
void mksnamespace::print_units(ostream &out,int M, int K, int S, int C) const
{
while(M || K || S || C)
{
int ipow = 0;
const struct _mks_units_list *close = closest(M,K,S,C,&ipow);
out << close->name << "**" << ipow << " ";
M -= close->M * ipow;
K -= close->K * ipow;
S -= close->S * ipow;
C -= close->C * ipow;
// cout << "ipow:" << ipow ; // TEMP
// cout << "Remaining M,K,S,C == " << M << " " << K << " " << S << "
" << C << " break " ; // TEMP
// break; // TEMP
}
}
#if 0
void mksnamespace::print_units(ostream &out,int M, int K, int S, int C)
{
for(struct _mks_units_list *ptr = mks_units_list;ptr;ptr = ptr->next)
if (ptr->M == M && ptr->K == K && ptr->S == S && ptr->C == C)
{
out << ptr->name;
return;
}
out << " met^" << M << " kg^" << K << " sec^" << S << " coul^" << C ;
}
#endif
ostream & mksnamespace::operator >> (ostream &out)
{
out << "mksnamespace: { ";
out << endl;
for(struct _mks_units_list *ptr = mks_units_list;ptr;ptr = ptr->next)
{
out << "[M:" << ptr->M;
out << " ; K:" << ptr->K;
out << " ; S:" << ptr->S;
out << " ; C:" << ptr->C;
out << " ; name:" << ptr->name << " ] ";
out << endl;
}
out << " } :ecapsemanskm";
return out;
}
- ----------------mks.h ------------------------------
#ifndef __MKS__
#define __MKS__
#include
#include "mksname.h"
template
class mks
{
double value;
public:
mks (const double &v) : value(v) {};
double getval (void) const { return value; };
template mks operator * (const
mks &rhs) const
{
return mks(value * rhs.getval());
};
template mks operator /
(const mks &rhs) const
{
return mks(value / rhs.getval());
};
};
template inline ostream & operator << (ostream
&out, const mks &m)
{
out << m.getval();
out << " ";
the_namespace.print_units(out,M,K,S,C);
return out;
};
#endif
- -------------- mks.c++ ------------------
// -*- compile-command : "g++ -o mks mks.c++ mksname.c++" -*-
#include "mks.h"
static mksnamespace the_namespace;
static mksnamespace::mksentry<2,1,-3,0> watts_entry(the_namespace,"watts");
static mksnamespace::mksentry<2,1,-2,0> joules_entry(the_namespace,"joules");
static mksnamespace::mksentry<2,1,-2,-1> volts_entry(the_namespace,"volts");
static mksnamespace::mksentry<-1,1,-2,0> pascals_entry(the_namespace,"pascals");
static mksnamespace::mksentry<1,1,-2,0> newtons_entry(the_namespace,"newtons");
static mksnamespace::mksentry<2,1,-1,-2> ohms_entry(the_namespace,"ohms");
static mksnamespace::mksentry<0,0,-1,1> amps_entry(the_namespace,"amps");
static mksnamespace::mksentry<-2,-1,2,2> farads_entry(the_namespace,"farads");
static mksnamespace::mksentry<-2,-1,1,2> siemens_entry(the_namespace,"siemens");
static mksnamespace::mksentry<0,1,-1,-1> teslas_entry(the_namespace,"teslas");
static mksnamespace::mksentry<2,1,-1,-1> weber_entry(the_namespace,"weber");
static mksnamespace::mksentry<2,1,0,-2> henry_entry(the_namespace,"henrys");
static mksnamespace::mksentry<0,1,0,0> kilo_entry(the_namespace,"kilograms");
static mksnamespace::mksentry<1,0,0,0> meter_entry(the_namespace,"meters");
static mksnamespace::mksentry<0,0,1,0> seconds_entry(the_namespace,"seconds");
static mksnamespace::mksentry<0,0,-1,0> hertz_entry(the_namespace,"hertz");
static mksnamespace::mksentry<0,0,0,1> coulombs_entry(the_namespace,"coulombs");
// static mksnamespace::mksentry<0,0,0,0> null_entry(the_namespace," ");
int main (int argc, char **argv)
{
mks<1,0,0,0> metervalue = 63.5;
mks<1,1,-2,0> newtvalue = 83.7;
mks<2,1,-2,0> nmvalue = 63.5 * 83.7;
cout << "meters translates to " ;
cout << metervalue;
cout << endl;
cout << "Newtons translates to " << newtvalue << endl;
cout << "Newton-meters translates to " << nmvalue << endl;
mks<2,1,-3,0> wattvalue = 82.3;
mks<0,0,1,0> secondsvalue = 2;
cout << "Watts translates to " << wattvalue << endl;
cout << "Seconds translates to " << secondsvalue << endl;
cout << "Watt-seconds translates to " << wattvalue * secondsvalue << endl;
mks<2,1,-4,0> wattspersec = 41.65;
cout << "Watts per second translates to " << wattspersec << endl;
mks<2,1,-1,-2> ohms = 76.3;
cout << "ohms translates to " << ohms << endl;
mks<3,1,-1,-2> ohm_meters = 111.0;
cout << "ohm-meters translates to " << ohm_meters << endl;
mks<2,1,-2,-1> volts = 120.0;
cout << "Volts translates to " << volts << endl;
mks<3,1,-2,-1> volt_meters = 42.6;
cout << "volt_meters translate to " << volt_meters << endl;
mks<1,1,-2,-1> volts_per_meter = 12.0;
mks<1,1,-1,-2> ohms_per_meter = 7.63;
cout << " volts per meter translates to " << volts_per_meter << endl;
cout << "ohms per meter translates to " << ohms_per_meter << endl;
mks<1,1,-2,-2> ohms_per_meter_per_second = 88.5;
cout << "ohms per meter per second translate to " << ohms_per_meter_per_second
<< endl;
mks<2,2,-4,0> newtons_squared = 100.0;
cout << "Newtons squared translates to " << newtons_squared << endl;
mks<4,2,-2,-4> ohms_squared = 100.0;
cout << "Ohms squared translates to " << ohms_squared << endl;
mks<6,3,-3,-6> ohms_cubed = 1000.0;
cout << "Ohms cubed translates to " << ohms_cubed << endl;
mks<6,3,-3,-7> ohms_cubed_per_coulomb = 500.0;
cout << "Ohms cubed per coulomb is " << ohms_cubed_per_coulomb << endl;
return 0;
// cout << the_namespace << endl;
};
- ------------------------------------- END ------------------
- ------------------------------------------------------------------------------------
For the state-based app
Watts, Simon (UK) wrote:
> > From: Terje Slettebų [mailto:tslettebo@si-units]
> > How is the Angle type defined, then? Is the history captured
> > in it, as part of its type? That's what I meant, if Angle captures the
> > history in its type,
> > or if the history is only available in the expression, not after being
> > assigned to such a quantity, like Angle. If so, then it should be ok.
>
> History is part of the type.
>
> I compose complex dimensions using a 'Dimension' template, such that
> a Quantity is defined as 'Unit'. 'Dimension<>', 'D1',
> and 'D2' all support the "dimension concept", so this gives the binary tree
> of the composition history.
>
> The resultant types for arithmetic operations are quantity types with the
> appropriate composition of dimensions: U * U => U ,
> etc.
>
> Since composition history is part of the type, assignment to another type
> (of the same dimensionality) does not preserve the history. I think this is
> right, as the user is placing a specific interpretation on the quantity.
>
> So, 'Acceleration / Velocity' gives '1/Time', which is not a Frequency (Hz)
> or Activity (Bq) quantity unless the user allocates to such (or performs a
> cast).
>
> We can also take advantage of this to force representations for composite
> dimensions:
>
> typedef Dimension angular_momentum_d;
>
> The printing of a quantity based on 'angular_momentum_d' would then be
> represented by symbols for force and length (N m).
>
> Now, this does clash with the definition of Energy (force * distance), so
> all library-provided types will have to use unique dimensionless tags to
> distinguish them from arbitary user compositions:
>
> typedef Dimension energy_d;
>
> Where 'enery_tag' is a third, optional (default = no_tag?) parameter which
> is only used to make distinct types with the same composition.
>
> One thing that occurs to me is, just how often will people be streaming out
> the results of inlined equations?
> cout << length1 * time2 / energy3 << endl;
>
> Si.
>
------------------------------
End of si-units-digest V1 #18
*****************************