Version 1.1 of ais/ai-00324.txt

Unformatted version of ais/ai-00324.txt version 1.1
Other versions for file ais/ai-00324.txt

!standard A.16 (01)          03-02-01 AI95-00324/01
!class amendment 03-02-01
!status work item 03-02-01
!status received 03-02-01
!priority Medium
!difficulty Medium
!subject Physical Units Checking
!summary
A new set of packages and generic packages is proposed that provide compile-time checking for calculations involving physical units.
!problem
Ada is one of the very few languages that provides strong type checking for numeric types. Unfortunately, the checking Ada provides for multiplicative operators is often not correct when performing calculations involving physical quantities. Such quantities are generally measured using units such as centimeters, grams, seconds, miles-per-hour, etc. Multplication and division are only meaningful when the units "work out" properly. Exponentiation is of course only meaningful when the corresponding multiplication would have been meaningful.
!proposal
A new set of packages and generic packages is proposed, all in a new hierarchy "Ada.Units.*", which together provide support for compile-time checking of calculations involving physical quantities.
Note: This is a joint proposal from Tucker Taft and Thom Brooke.
-----
The package Ada.Units forms the root of the hierarchy devoted to physical units checking.
package Ada.Units is subtype Exponent_Type is Integer; type Scale_Type is digits 11; -- Implementation defined end Ada.Units;
Exponent_Type is used to define the exponent for a physical unit along each dimension (length, mass, time, charge, etc.).
-----
The generic package Ada.Units.Dimension_Exponents defines the Exponent_Array type, which is used throughout to describe the dimensions associated with a physical unit.
generic type Names_Of_Dimensions is (<>); package Ada.Units.Dimension_Exponents is -- Names_Of_Dimensions should be an enumeration type; -- Each enumeration literal corresponds to one dimension -- in the system of units.
-- NOTE: It is recommended that each enumeration literal -- be a generic dimension name like "length" or "time" so it -- won't collide with the name of a specific unit such as "cm" -- which would be the typical name for an instance of the -- "Unit" generic.
type Exponent_Array is array(Names_Of_Dimensions) of Exponent_Type;
Number_Of_Dimensions : constant Integer := Exponent_Array'Length;
function "+"(Left, Right : Exponent_Array) return Exponent_Array; function "-"(Left, Right : Exponent_Array) return Exponent_Array; function "-"(Right : Exponent_Array) return Exponent_Array; -- Component-wise "+" and "-" end Ada.Units.Dimension_Exponents;
The enumeration type Names_Of_Dimensions provides names for the various dimensions in the system of units being defined. Typically, this type might be defined with the enumeration literals such as "Length", "Mass", "Time", etc.
The operators on the exponent array type are used within Assert pragmas to verify that the units passed into a generic are related appropriately.
-----
The package Ada.Units.Name_Support provides a set of default name construction utilities which are used in the absence of an explicit specification of a name for a "scaled" unit, a "product" unit, or a "quotient" unit. See System_Of_Units below for what it means to be a scaled, product, or quotient unit.
package Ada.Units.Name_Support is -- Package of string utilities useful for generating -- names automatically
function Scale_Prefix(Scale_Factor : Scale_Type) return String; -- Return appropriate prefix given scale factor. -- (e.g. "kilo" for 1000.0) -- If scale factor is not something common, then return -- Scale_Type'Image(Scale_Factor) & '*'
function Short_Scale_Prefix(Scale_Factor : Scale_Type) return String; -- Return appropriate short prefix given scale factor. -- (e.g. "K" for 1000.0) -- If scale factor is not something common, then return "?"
function Prefixed_Name(Unit_Name : String; Relative_Scale : Scale_Type) return String; -- Add a prefix to name to account for scale factor -- E.g., if Relative_Scale = 1000.0, return "kilo" -- or "K" depending on length of Unit_Name ("K" if -- Unit_Name <= 3 chars in length, "kilo" otherwise)
function Pluralize(Singular_Name : String) return String; -- Return plural form given singular form. -- In general returns Singular_Name unchanged -- if it is <= 2 characters, and adds an 's' otherwise. -- TBD: Internationalization
function Product_Name(Unit_A, Unit_B : String) return String; -- Return default name for Product_Unit, given name -- of two units forming the product. -- Currently just returns Unit_A & '-' & Unit_B
function Quotient_Name(Numerator, Denominator : String) return String; -- Return default name for Quotient_Unit, given name -- of numerator and denominator -- Currently just returns Numerator & '/' & Denominator
function Is_Quotient_Name(Name : String) return Boolean; -- Return true if name consists of numerator and denominator -- with separating '/'
function Numerator_Part(Quotient : String) return String; -- Return numerator part of name constructed via -- Quotient_Name
function Denominator_Part(Quotient : String) return String; -- Return denominator part of name constructed via -- Quotient_Name
end Ada.Units.Name_Support;
-----
The generic package Ada.Units.System_Of_Units provides the primary support for physical units checking. It provides nested generics for creating particular units that together form a single "system" of units, such as "CGS" (cm, g, sec) or "MKS" (meter, kg, sec). A typical use would first instantiate System_Of_Units, and then instantiate the nested generic Unit, once for each unit of interest, such as "cm" or "g" or "kph". See the examples for further understanding.
with Ada.Text_IO; -- for text_output with Ada.Units.Name_Support; with Ada.Units.Dimension_Exponents; generic type Names_Of_Dimensions is (<>); type Value_Type is digits <>; package Ada.Units.System_Of_Units is -- Instantiate this package for each system of units
-- Names_Of_Dimensions should be an enumeration type; -- Each enumeration literal corresponds to one dimension -- in the system of units. -- Value_Type is floating-point type that will be parent type -- for all types declared in this package.
package Dimensions is new Ada.Units.Dimension_Exponents(Names_Of_Dimensions);
use Dimensions;
generic Name : in String; Exponents : in Exponent_Array; Scale_Factor : in Scale_Type := 1.0; type Value is digits <>; package Unit_Signature is end Unit_Signature; -- This is a generic "signature" package used -- to allow uniform use of the various types -- declared below. -- Scale_Factor is scaling factor. It is expressed -- in the base units (e.g. grams, meters, whatever). -- Every value is implicitly multiplied by this scale -- to give its value in the base unit. -- So for milliseconds the scale would be 0.001 presuming -- the base unit is seconds.
package Unitless is -- These types are unitless (aka dimensionless) type Value is new Value_Type'Base; -- Multiplicative operators are OK on Unitless types
-- Define other parameters used in signature package Name : constant String := "(unitless)"; Exponents : constant Exponent_Array := (others => 0); Scale_Factor : constant Scale_Type := 1.0;
package Signature is new Unit_Signature( Name, Exponents, Scale_Factor, Value);
package Sig renames Signature; -- for convenience
end Unitless;
-- Define a type to be used as a base for further derivations -- and provide the operators that combine with the unitless type type Base_For_Derivation is new Value_Type'Base; function "*"(Left : Base_For_Derivation; Right : Unitless.Value) return Base_For_Derivation; function "*"(Left : Unitless.Value; Right : Base_For_Derivation) return Base_For_Derivation; function "/"(Left : Base_For_Derivation; Right : Unitless.Value) return Base_For_Derivation; function "/"(Left : Base_For_Derivation; Right : Base_For_Derivation) return Unitless.Value;
-- And make the predefined multiplicative operators abstract function "*"(Left, Right : Base_For_Derivation) return Base_For_Derivation is abstract; function "/"(Left, Right : Base_For_Derivation) return Base_For_Derivation is abstract; function "**"(Left : Base_For_Derivation; Right : Integer) return Base_For_Derivation is abstract;
pragma Inline("*", "/");
generic Name : in String; Exponents : in Exponent_Array; package Unit is -- Instantiate this generic once for each distinct base unit -- Name is default name to use for unit on output (singular) -- Exponents is array of powers of each dimension
-- This package is for units that have some non-zero dimension -- See Dimensionless_Unit below for case -- where exponents are all zero pragma Assert(Exponents /= Unitless.Exponents);
type Value is new Base_For_Derivation; -- Inherit ops with unitless type
-- Scale_Factor of base unit is always 1.0 Scale_Factor : constant Scale_Type := 1.0;
package Signature is new Unit_Signature( Name, Exponents, Scale_Factor, Value); -- Provide signature package for use with other generics
package Sig renames Signature; -- for convenience
end Unit;
generic with package Unit_A is new Unit_Signature(<>); with package Unit_B is new Unit_Signature(<>); package Scaling is -- Instantiate this to get scaling functions -- between units representing same physical dimensions -- but with different scale factors
-- Note that this is instantiated as part of -- defining a scaled unit below, providing -- scaling functions between the base unit -- and the scaled unit. Instantiate this directly -- to get additional scaling functions between two scaled units -- (with the same base unit).
pragma Assert(Unit_A.Exponents = Unit_B.Exponents); function Scale(From : Unit_A.Value) return Unit_B.Value; function Scale(From : Unit_B.Value) return Unit_A.Value;
pragma Inline(Scale); end Scaling;
generic with package Base_Unit is new Unit_Signature(<>); Relative_Scale : in Scale_Type; Name : in String := Name_Support.Prefixed_Name(Base_Unit.Name, Relative_Scale); package Scaled_Unit is -- Instantiate this package to create a unit -- with the same dimensions as the base unit, -- but a different scale factor -- Relative_Scale is scale factor relative to base unit -- "Absolute" scale factor is -- Relative_Scale * Base_Unit.Scale_Factor
type Value is new Base_For_Derivation; -- Inherit ops with Unitless type -- (no need to override ops because no scaling required -- when combining with unitless type)
-- Define other parameters to signature Scale_Factor : constant Scale_Type := Relative_Scale * Base_Unit.Scale_Factor;
Exponents : Exponent_Array renames Base_Unit.Exponents;
package Signature is new Unit_Signature( Name, Exponents, Scale_Factor, Value); -- Provide signature for use with other generics
package Sig renames Signature; -- for convenience
package Scaling is new Ada.Units.System_Of_Units.Scaling( Base_Unit, Scaled_Unit.Signature); -- Get scaling functions between base unit and scaled unit
-- And rename them for direct visibility function Scale(From : Base_Unit.Value) return Scaled_Unit.Value renames Scaling.Scale; function Scale(From : Scaled_Unit.Value) return Base_Unit.Value renames Scaling.Scale; -- also note that unary "+" can be used for this scaling -- (see below)
-- Define the appropriate additive operators -- NOTE: Except for unary "+", these all return Base_Unit.Value to -- avoid ambiguity in complicated expressions. function "+"(Left : Base_Unit.Value; Right : Scaled_Unit.Value) return Base_Unit.Value; function "+"(Left : Scaled_Unit.Value; Right : Base_Unit.Value) return Base_Unit.Value; function "+"(Right : Scaled_Unit.Value) return Base_Unit.Value renames Scaling.Scale; -- Unary "+" can be used for scaling.
function "+"(Right : Base_Unit.Value) return Scaled_Unit.Value renames Scaling.Scale; -- Unary "+" can be used for scaling.
function "-"(Left : Base_Unit.Value; Right : Scaled_Unit.Value) return Base_Unit.Value; function "-"(Left : Scaled_Unit.Value; Right : Base_Unit.Value) return Base_Unit.Value; function "-"(Right : Scaled_Unit.Value) return Base_Unit.Value;
pragma Inline("+", "-");
end Scaled_Unit;
generic with package Unit_A is new Unit_Signature(<>); with package Unit_B is new Unit_Signature(<>); with package Unit_C is new Unit_Signature(<>); package Multiplicative_Operators is -- Instantiate this package with three units -- where A * B = C is well defined to get -- the appropriate multiplicative operators. -- Scaling is performed automatically. -- NOTE: This is instantiated below in Product_Unit -- and Quotient_Unit. Those are the preferred way -- for getting these cross-unit operators.
pragma Assert( Unit_A.Exponents + Unit_B.Exponents = Unit_C.Exponents);
function "*"(Left : Unit_A.Value; Right : Unit_B.Value) return Unit_C.Value; function "*"(Left : Unit_B.Value; Right : Unit_A.Value) return Unit_C.Value; function "/"(Left : Unit_C.Value; Right : Unit_A.Value) return Unit_B.Value; function "/"(Left : Unit_C.Value; Right : Unit_B.Value) return Unit_A.Value;
pragma Inline("*", "/"); end Multiplicative_Operators;
generic with package Unit_A is new Unit_Signature(<>); with package Unit_B is new Unit_Signature(<>); Name : in String := Name_Support.Product_Name(Unit_A.Name, Unit_B.Name); package Product_Unit is -- Instantiate this package for a unit that is -- the product of two other units. -- The Exponents of the new type is the sum of those for -- Unit_A and Unit_B. -- The Scale_Factor for the new type is the product of the scales -- of Unit_A and Unit_B. -- Use Scaled_Unit to get other scalings. -- The default Name is <unit_a>-<unit_b> (e.g. "foot-lb")
type Value is new Base_For_Derivation; -- Get ops with unitless type, etc.
-- provide other parameters of signature Scale_Factor : constant Scale_Type := Unit_A.Scale_Factor * Unit_B.Scale_Factor;
Exponents : constant Exponent_Array := Unit_A.Exponents + Unit_B.Exponents;
package Signature is new Unit_Signature( Name, Exponents, Scale_Factor, Value);
package Sig renames Signature; -- for convenience
-- Get appropriate multiplicative operators package Ops is new Multiplicative_Operators( Unit_A, Unit_B, Product_Unit.Signature);
-- Rename operators for direct visibility function "*"(Left : Unit_A.Value; Right : Unit_B.Value) return Product_Unit.Value renames Ops."*"; function "*"(Left : Unit_B.Value; Right : Unit_A.Value) return Product_Unit.Value renames Ops."*"; function "/"(Left : Product_Unit.Value; Right : Unit_A.Value) return Unit_B.Value renames Ops."/"; function "/"(Left : Product_Unit.Value; Right : Unit_B.Value) return Unit_A.Value renames Ops."/";
end Product_Unit;
generic with package Unit_A is new Unit_Signature(<>); with package Unit_B is new Unit_Signature(<>); Name : in String := Name_Support.Quotient_Name(Unit_A.Name, Unit_B.Name); package Quotient_Unit is -- Instantiate this package for a unit that is -- the quotient of two other units. -- The Exponents of the new type is the difference of those for -- Unit_A and Unit_B. -- The Scale_Factor for the new type is the ratio of the scales -- of Unit_A and Unit_B. -- Use Scaled_Unit to get other scalings. -- The default Name is <unit_a>/<unit_b> (e.g. "cm/sec")
type Value is new Base_For_Derivation; -- Get ops with unitless type, etc.
-- provide other parameters of signature Scale_Factor : constant Scale_Type := Unit_A.Scale_Factor / Unit_B.Scale_Factor;
Exponents : constant Exponent_Array := Unit_A.Exponents - Unit_B.Exponents;
package Signature is new Unit_Signature( Name, Exponents, Scale_Factor, Value);
package Sig renames Signature; -- for convenience
-- Get appropriate multiplicative operators package Ops is new Multiplicative_Operators( Quotient_Unit.Signature, Unit_B, Unit_A);
-- Rename operators for direct visibility function "*"(Left : Quotient_Unit.Value; Right : Unit_B.Value) return Unit_A.Value renames Ops."*"; function "*"(Left : Unit_B.Value; Right : Quotient_Unit.Value) return Unit_A.Value renames Ops."*"; function "/"(Left : Unit_A.Value; Right : Quotient_Unit.Value) return Unit_B.Value renames Ops."/"; function "/"(Left : Unit_A.Value; Right : Unit_B.Value) return Quotient_Unit.Value renames Ops."/";
end Quotient_Unit;
generic Name : in String; package Dimensionless_Unit is -- Instantiate this generic once for each -- distinct dimensionless base unit (e.g. radians), -- where multiplication and division of the unit -- with itself are well defined (and hence exponentiation makes -- sense). -- Name is default name to use for unit on output (singular).
type Value is new Value_Type'Base; -- All operators inherited, including multiply, divide, and -- exponentiation.
-- Exponents all zero Exponents : Exponent_Array renames Unitless.Exponents;
-- Scale_Factor of base unit is always 1.0 Scale_Factor : constant Scale_Type := 1.0;
package Signature is new Unit_Signature( Name, Exponents, Scale_Factor, Value); -- Provide signature package for use with other generics
package Sig renames Signature; -- for convenience
-- NOTE: We do not provide the operators that combine -- with the unitless type as they create too much ambiguity
end Dimensionless_Unit;
-- Ideally this would be a generic child to avoid -- the main generic from having a dependence on Text_IO. -- Unfortunately, GNAT 3.14p can't deal with this as a child unit -- so at least until that problem is remedied, Text_Output -- is included as a nested generic rather than as a child.
-- with Ada.Units.Name_Support; -- with Ada.Text_IO; generic with package Unit_For_Output is new Unit_Signature(<>); Singular : in String := Unit_For_Output.Name; Plural : in String := Name_Support.Pluralize(Singular); -- Name for unit, plural form Fore : Ada.Text_IO.Field := 2; Aft : Ada.Text_IO.Field := 3; -- 3 digits after decimal pt Exp : Ada.Text_IO.Field := 0; -- No exponent by default package Text_Output is -- Ada.Units.System_Of_Units.Text_Output is -- Instantiate this for text output (and Image) with units included -- Singular is label when Val = 1.0 -- Plural is label in other cases -- Fore is minimum digits before decimal point -- Aft is digits after decimal point -- Exp is number of digits allowed for exponent -- (as used in Float_IO.Put)
use Unit_For_Output;
procedure Put(Val : in Value); procedure Put(File : in Ada.Text_IO.File_Type; Val : in Value); function Image(Val : in Value) return String;
end Text_Output; -- end Ada.Units.System_Of_Units.Text_Output;
end Ada.Units.System_Of_Units;
This generic package is instantiated with a "base" floating point type to be used for all unit calculations, and the names for the various dimensions of interest to the system of units.
Throughout System_Of_Units, a unit is represented using an instantiation of the generic "signature" package Unit_Signature. A generic signature package is one that is instantiated simply so it can be used as an actual parameter for a formal package parameter.
Particular units within the system of units are defined using the nested generic packages Unit, Scaled_Unit, Product_Unit, Quotient_Unit, and Dimensionless_Unit. These generic packages automatically provide appropriate multiplicative and additive operators between the units, which do scaling as necessary, and ensure that only meaningfuul combinations of units are performed. They also provide an instantiation of the Unit_Signature generic signature package so that the newly defined unit may be used with other generics within System_Of_Units. This signature is available with the name "Signature," and with the abbreviation "Sig."
Additional operations may be created by instantiating the nested generic packages Scaling and Multiplicative_Operators, though this will generally not be necessary for most uses, as the automatically provided operators will usually be sufficient.
Finally, there is a nested generic package Text_Output which provides an Image function and text output that includes the name of the unit as part of the output. Note that this package might better be a generic child unit, but not all compilers seem to be able to handle that yet, when combined with the use of generic signatures.
!wording
(See proposal.)
!example
Here is a simple example of use of the System_Of_Units generic, and its nested generics.
with Ada.Text_IO; with Ada.Units.system_of_units; -- with Ada.Units.System_Of_Units.Text_Output; use Ada.Units; procedure test_units is type dim_names is (length, mass, time);
package cgs is new system_of_units(dim_names, Standard.Float); use type cgs.unitless.value;
package cm is new cgs.unit("cm", (length => 1, others => 0)); package cm_output is new cgs.text_output(cm.signature);
package g is new cgs.unit("g", (mass => 1, others => 0)); package g_output is new cgs.text_output(g.sig);
package sec is new cgs.unit("sec", (time => 1, others => 0)); package sec_output is new cgs.text_output(sec.signature);
package cps is new cgs.quotient_unit(cm.sig, sec.sig); package cps_output is new cgs.text_output(cps.signature);
package meter is new cgs.scaled_unit( cm.sig, relative_scale => 100.0, name => "meter"); package meter_output is new cgs.text_output(meter.sig);
package kg is new cgs.scaled_unit( base_unit => g.sig, relative_scale => 1000.0); package kg_output is new cgs.text_output(kg.sig);
package usec is new cgs.scaled_unit( base_unit => sec.sig, relative_scale => 1.0E-6); package usec_output is new cgs.text_output(usec.sig);
package msec is new cgs.scaled_unit(base_unit => sec.sig, relative_scale => 1.0E-3);
X : meter.value := 27.0;
Y : cm.value := 540.0;
Speed : cps.value; -- cm/sec Interval : sec.value := 33.0; -- seconds
use type cm.value, meter.value; use type cps.value; begin meter_output.put(X); ada.text_io.put(" + "); cm_output.put(Y); ada.text_io.put(" = "); cm_output.put(X + Y); ada.text_io.new_line; speed := (X + Y)/Interval; ada.text_io.put_line("If traveled in " & sec_output.image(Interval) & " then speed = " & cps_output.image(speed));
ada.text_io.put_line("Three kilograms = " & kg_output.image(3.0)); ada.text_io.put_line("Two and a half microsecs = " & usec_output.image(2.5)); end test_units;
Here is the output produced by compiling and running this test:
27.000 meters + 540.000 cm = 3240.000 cm If traveled in 33.000 secs then speed = 98.182 cm/sec Three kilograms = 3.000 Kg Two and a half microsecs = 2.500 usecs
Points to note about the output: - Text_Output by default has 3 digits after decimal point and no
exponent. This can be overridden when instantiating it.
- Text_Output by default uses the name of the unit provided
when the unit was first created.
- For scaled, product, and quotient units, the name of the unit is by
default constructed from the name(s) of the base unit(s), using the Name_Support routines. Examples of these automatically constructed names are "Kg", "usec" and "cm/sec".
!discussion
As mentioned in the "problem" statement, Ada's strong numeric type checking does not work very well for multiplicative operators in some cases, as it always allows unit-A * unit-A => unit-A, while disallowing unit-A * unit-B => unit-C even when meaningful. However, the ability to make certain operators "abstract," combined with the ability to define new numeric types and new cross-type definitions for the multiplicative operators can provide a solution.
Unfortunately, manually building up a set of types with appropriate cross-type multiplicative operators is error prone and tedious. Various approaches have been tried in the past, but using generic "signature" packages and appropriate pragma Assert's has not been tried in combination before, as far as we know.
In any case, whether this approach is novel or not, it becomes significantly more valuable if there is one, standard way of doing it, with one standard generic signature package, etc. This will allow more functionality (such as periodicity, more sophisticated input and output routines, etc.) to be layered on top in the future, and built into other shared abstractions.
Other approaches that were considered involved special pragmas combined with using Ada subtypes rather than derived types, special attributes, zero-space-overhead discriminants, etc. Essentially all of these approaches involved significant compiler development work, and it is unclear whether the added capability could justify the inevitably high cost of this development.
By contrast, this approach uses features already supported by Ada 95 compilers (albeit perhaps in ways that may stretch the capability of some compilers ;-), and seems to provide almost all the capability that additional language features, pragmas, or attributes could provide.
There are a few limitations of the current proposal that we know how to fix, but are not sure whether that is wise at this point. First of all, numeric literals and explicit type conversion are available for the types associated with units. This to some degree reduces the safety guarantees provided. If the types were made into private types, we could make stronger guarantees. However, you would then not be able to use the types in existing generics that require floating point types. That also might be considered a good thing or a bad thing. For instance it is not clear it makes sense to instantiate Generic_Elementary_Functions with a type representing centimeters.
It would be possible to use private types. There are some technical difficulties when combining private types and generic signature instantiations (which might become the subject of another AI), but it is possible to work around these problems. [Basically, you need to define the private type completely in a subpackage, and then derive from it in the package where the instantiation of the signature takes place.]
Another limitation is that the units are only for floating point types. This could also be solved, by having a more elaborate set of formal parameters to System_Of_Units, perhaps a private type and a bunch of operators, or an instanace of a signature for a numeric-ish type. Probably the most interesting non-floating-point type would be type Complex, though conceivably users of fixed-point types or even integer types might find this to be a useful approach to units checking. Of course, since it is just a "normal" generic package with no magic, it can be used a model for complex, integer, or fixed-point versions.
Another important issue is whether "pre-instantiations" should be supplied for common systems of units (e.g. "CGS"). This might lower the entry barrier to use, and would provide "standard" examples as models as well.
Here is an example of such a pre-instantiation, and it should probably be considered as an explicit option in this proposal:
with Ada.Units.System_Of_Units; pragma Elaborate_All(Ada.Units.System_Of_Units); package Ada.Units.CGS_Units is
type Dimensions is (Length, Mass, Time);
package CGS is new System_Of_Units( Names_Of_Dimensions => Dimensions, Value_Type => Standard.Float);
package cm is new CGS.Unit( Name => "cm", Exponents => (Length => 1, others => 0));
package g is new CGS.Unit( Name => "g", Exponents => (Mass => 1, others => 0));
package sec is new CGS.Unit( Name => "sec", Exponents => (Time => 1, others => 0));
end Ada.Units.CGS_Units;
!appendix

Here is a prototypical implementation of the proposed packages:

--------------------

package body Ada.Units.Dimension_Exponents is
     function "+"(Left, Right : Exponent_Array) return Exponent_Array is
         -- Component-wise "+"
         Result : Exponent_Array;
     begin
         for I in Result'Range loop
             Result(I) := Left(I) + Right(I);
         end loop;
         return Result;
     end "+";

     function "-"(Left, Right : Exponent_Array) return Exponent_Array is
         -- Component-wise "-"
         Result : Exponent_Array;
     begin
         for I in Result'Range loop
             Result(I) := Left(I) - Right(I);
         end loop;
         return Result;
     end "-";

     function "-"(Right : Exponent_Array) return Exponent_Array is
         -- Component-wise unary "-"
         Result : Exponent_Array;
     begin
         for I in Result'Range loop
             Result(I) := - Right(I);
         end loop;
         return Result;
     end "-";
end Ada.Units.Dimension_Exponents;

package body Ada.Units.Name_Support is
     -- Package of string utilities useful for generating
     -- names automatically

     function Scale_Prefix(Scale_Factor : Scale_Type) return String is
       -- Return appropriate prefix given scale factor.
       -- (e.g. "kilo" for 1000.0)
       -- If scale factor is not something common, then return
       -- Scale_Type'Image(Scale_Factor) & '*'
     begin
         if Scale_Factor = 1.0 then
             return "";
         elsif Scale_Factor = 10.0 then
             return "deca";
         elsif Scale_Factor = 100.0 then
             return "heca";
         elsif Scale_Factor = 1000.0 then
             return "kilo";
         elsif Scale_Factor = 1.0E6 then
             return "mega";
         elsif Scale_Factor = 1.0E9 then
             return "giga";
         elsif Scale_Factor = 1.0E12 then
             return "tera";
         elsif Scale_Factor = 1.0E15 then
             return "peta";
         elsif Scale_Factor = 0.1 then
             return "deci";
         elsif Scale_Factor = 0.01 then
             return "centi";
         elsif Scale_Factor = 0.001 then
             return "milli";
         elsif Scale_Factor = 1.0E-6 then
             return "micro";
         elsif Scale_Factor = 1.0E-9 then
             return "nano";
         elsif Scale_Factor = 1.0E-12 then
             return "pico";
         else
             return Scale_Type'Image(Scale_Factor) & '*';
         end if;
     end Scale_Prefix;

     function Short_Scale_Prefix(Scale_Factor : Scale_Type) return String is
       -- Return appropriate short prefix given scale factor.
       -- (e.g. "K" for 1000.0)
       -- If scale factor is not something common, then return
       -- Scale_Type'Image(Scale_Factor) & '*'
     begin
         if Scale_Factor = 1.0 then
             return "";
         elsif Scale_Factor = 10.0 then
             return "D";
         elsif Scale_Factor = 100.0 then
             return "H";
         elsif Scale_Factor = 1000.0 then
             return "K";
         elsif Scale_Factor = 1.0E6 then
             return "M";
         elsif Scale_Factor = 1.0E9 then
             return "G";
         elsif Scale_Factor = 1.0E12 then
             return "T";
         elsif Scale_Factor = 1.0E15 then
             return "P";
         elsif Scale_Factor = 0.1 then
             return "d";
         elsif Scale_Factor = 0.01 then
             return "c";
         elsif Scale_Factor = 0.001 then
             return "m";
         elsif Scale_Factor = 1.0E-6 then
             return "u";
         elsif Scale_Factor = 1.0E-9 then
             return "n";
         elsif Scale_Factor = 1.0E-12 then
             return "p";
         else
             return "?";
         end if;
     end Short_Scale_Prefix;

     function Prefixed_Name(Unit_Name : String; Relative_Scale : Scale_Type)
       return String is
       -- Add a prefix to name to account for scale factor
       -- E.g., if Relative_Scale = 1000.0, return "kilo"
       -- or "K" depending on length of Unit_Name ("K" if
       -- Unit_Name <= 3 chars in length, "kilo" otherwise)
     begin
         if Unit_Name'Length <= 3 then
             return Short_Scale_Prefix(Relative_Scale) & Unit_Name;
         else
             return Scale_Prefix(Relative_Scale) & Unit_Name;
         end if;
     end Prefixed_Name;


     function Product_Name(Unit_A, Unit_B : String)
       return String is
         -- Return default name for Product_Unit, given name
         -- of two units forming the product.
         -- Currently just returns Unit_A & '-' & Unit_B
     begin
         return Unit_A & '-' & Unit_B;
     end Product_Name;

     function Quotient_Name(Numerator, Denominator : String)
       return String is
         -- Return default name for Quotient_Unit, given name
         -- of numerator and denominator
         -- Currently just returns Numerator & '/' & Denominator
     begin
         return Numerator & '/' & Denominator;
     end Quotient_Name;

     function Is_Quotient_Name(Name : String) return Boolean is
       -- Return true if name consists of numerator and denominator
       -- with separating '/'
     begin
         for I in Name'Range loop
             if Name(I) = '/' then
                 return True;
             end if;
         end loop;
         return False;
     end Is_Quotient_Name;

     function Numerator_Part(Quotient : String) return String is
       -- Return numerator part of name constructed via
       -- Quotient_Name
     begin
         for I in Quotient'Range loop
             if Quotient(I) = '/' then
                 return Quotient(Quotient'First .. I-1);
             end if;
         end loop;
         return Quotient;  -- Should never happen
     end Numerator_Part;

     function Denominator_Part(Quotient : String) return String is
       -- Return denominator part of name constructed via
       -- Quotient_Name
     begin
         for I in Quotient'Range loop
             if Quotient(I) = '/' then
                 return Quotient(I+1 .. Quotient'Last);
             end if;
         end loop;
         return "??";  -- Should never happen
     end Denominator_Part;

     function Pluralize(Singular_Name : String) return String is
       -- Return plural form given singular form.
       -- In general returns Singular_Name unchanged
       -- if it is <= 2 characters, and adds an 's' otherwise.
       -- TBD: Internationalization
     begin
         if Singular_Name'Length <= 2 or else
           Singular_Name(Singular_Name'Last) = 's' then
             -- Short names generally don't add an 's'
             -- (e.g. "g").
             return Singular_Name;
         elsif Is_Quotient_Name(Singular_Name) then
             -- If is a quotient name, then pluralize the numerator
             -- part only
             return Quotient_Name(
               Pluralize(Numerator_Part(Singular_Name)),
               Denominator_Part(Singular_Name));
         else
             -- TBD: Internationalization
             return Singular_Name & 's';
         end if;
     end Pluralize;

end Ada.Units.Name_Support;

with Ada.Long_Float_Text_IO; -- implementation-dependent choice
with Ada.Text_IO;
package body Ada.Units.System_Of_Units is

     use type Unitless.Value;

       -- Provide the operators that combine with the unitless type
       -- Be careful not to recurse infinitely
       -- No scaling is necessary
     function "*"(Left : Base_For_Derivation; Right : Unitless.Value)
       return Base_For_Derivation is
     begin
         return Base_For_Derivation(Unitless.Value(Left) * Right);
     end "*";

     function "*"(Left : Unitless.Value; Right : Base_For_Derivation)
       return Base_For_Derivation is
     begin
         return Base_For_Derivation(Left * Unitless.Value(Right));
     end "*";

     function "/"(Left : Base_For_Derivation; Right : Unitless.Value)
       return Base_For_Derivation is
     begin
         return Base_For_Derivation(Unitless.Value(Left) / Right);
     end "/";

     function "/"(Left : Base_For_Derivation; Right : Base_For_Derivation)
       return Unitless.Value is
     begin
         return Unitless.Value(Left) / Unitless.Value(Right);
     end "/";

     package body Scaling is
         -- Instantiate this to get scaling functions
         -- between units representing same physical dimensions
         -- but with different scale factors

         function Scale(From : Unit_A.Value) return Unit_B.Value is
         begin
             return Unit_B.Value(Unit_A.Scale_Factor / Unit_B.Scale_Factor *
               Scale_Type(From));
         end Scale;

         function Scale(From : Unit_B.Value) return Unit_A.Value is
         begin
             return Unit_A.Value(Unit_B.Scale_Factor / Unit_A.Scale_Factor *
               Scale_Type(From));
         end Scale;

     end Scaling;

     package body Scaled_Unit is
         -- Define the appropriate additive operators
         -- NOTE: These all return Base_Unit.Value to avoid
         -- ambiguity in complicated expressions.

         use type Base_Unit.Value;

         function "+"(Left : Base_Unit.Value; Right : Scaled_Unit.Value)
           return Base_Unit.Value is
         begin
             return Left + Scale(Right);
         end "+";

         function "+"(Left : Scaled_Unit.Value; Right : Base_Unit.Value)
           return Base_Unit.Value is
         begin
             return Scale(Left) + Right;
         end "+";

         function "-"(Left : Base_Unit.Value; Right : Scaled_Unit.Value)
           return Base_Unit.Value is
         begin
             return Left - Scale(Right);
         end "-";
         function "-"(Left : Scaled_Unit.Value; Right : Base_Unit.Value)
           return Base_Unit.Value is
         begin
             return Scale(Left) - Right;
         end "-";
         function "-"(Right : Scaled_Unit.Value)
           return Base_Unit.Value is
         begin
             return - Scale(Right);
         end "-";

     end Scaled_Unit;

     package body Multiplicative_Operators is
         -- Scaling is performed automatically.

         Combined_Scale_Factor : constant Scale_Type :=
           Unit_A.Scale_Factor * Unit_B.Scale_Factor / Unit_C.Scale_Factor;
         Inverse_Combined_Scale_Factor : constant Scale_Type :=
           Unit_C.Scale_Factor / (Unit_A.Scale_Factor * Unit_B.Scale_Factor);

         function "*"(Left : Unit_A.Value; Right : Unit_B.Value)
           return Unit_C.Value is
         begin
             return Unit_C.Value(Combined_Scale_Factor *
               Scale_Type(Left) * Scale_Type(Right));
         end "*";
         function "*"(Left : Unit_B.Value; Right : Unit_A.Value)
           return Unit_C.Value is
         begin
             return Unit_C.Value(Combined_Scale_Factor *
               Scale_Type(Left) * Scale_Type(Right));
         end "*";
         function "/"(Left : Unit_C.Value; Right : Unit_A.Value)
           return Unit_B.Value is
         begin
             return Unit_B.Value(Inverse_Combined_Scale_Factor *
               Scale_Type(Left) / Scale_Type(Right));
         end "/";
         function "/"(Left : Unit_C.Value; Right : Unit_B.Value)
           return Unit_A.Value is
         begin
             return Unit_A.Value(Inverse_Combined_Scale_Factor *
               Scale_Type(Left) / Scale_Type(Right));
         end "/";

     end Multiplicative_Operators;

     -- with Ada.Long_Float_Text_IO; -- implementation-dependent choice
     -- GNAT can't deal with this as a generic child
     package body Text_Output is

         subtype IO_Type is Long_Float;  -- implementation-dependent choice
         package Float_IO renames Ada.Long_Float_Text_IO;
                    -- implementation-dependent choice

         function Name(Is_One : Boolean) return String is
           -- Return Singular or Plural, depending on
           -- whether Val = 1.0 (i.e. Is_One true)
         begin
             if Is_One then
                 return Singular;
             else
                 return Plural;
             end if;
         end Name;

         procedure Put(Val : in Value) is
         begin
             Float_IO.Put(IO_Type(Val),
               Fore => Fore, Aft => Aft, Exp => Exp);
             Ada.Text_IO.Put(' ' & Name(Is_One => Val = 1.0));
         end Put;

         procedure Put(File : in Ada.Text_IO.File_Type;
           Val : in Value) is
         begin
             Float_IO.Put(File, IO_Type(Val),
               Fore => Fore, Aft => Aft, Exp => Exp);
             Ada.Text_IO.Put(File, ' ' & Name(Is_One => Val = 1.0));
         end Put;

         function Image(Val : in Value) return String is
             Extra_Digits_Before_Decimal : constant := 30;
               -- This is extra room allowed in the Result string
               -- declared below.
               -- TBD: The value "30" allows for the case when EXP
               -- is zero. The value 30 is pretty arbitrary but
               -- should be more than sufficient.

             Result : String(1 .. Fore + Aft + Exp + 2 +
               Extra_Digits_Before_Decimal);
         begin
             Float_IO.Put(Result, IO_Type(Val),
               Aft => Aft, Exp => Exp);
             for I in Result'Range loop
                 if Result(I) /= ' ' then
                     return Result(I..Result'Last) & ' ' &
                       Name(Is_One => Val = 1.0);
                 end if;
             end loop;
             return "??";  -- Should never happen
         end Image;

     end Text_Output;

end Ada.Units.System_Of_Units;

!ACATS test

ACATS tests are needed to test this package throughly.

!appendix

From: Pascal Leroy
Sent: Monday, February 3, 2003  9:14 AM

I like it.

Random comments:

I wonder why you pass Names_Of_Dimensions and not an instantiation of
Dimension_Exponents to System_Of_Units.  If you work with several system of
units, it would seem that factoring the exponents could be useful.  (I am told
that there are countries were SI is not the only system of units in practical
use.)

I think this should not be restricted to floating-point types.  Fixed-point
types and complex come to mind.

References to GNAT bugs have nothing to do in an AI, unless we want to enshrine
them in the RM ;-)

The style of Text_Output, where you pass Fore/Aft/Exp at instantiation time, is
different from that of, say, Complex_IO.  I don't think that's a good idea.

A preinstantiation should be provided for the SI system.  Btw, some of the
examples will need to be sanitized to be in line with the SI conventions (s
instead of sec, K is for Kelvin, k is for kilo, etc.).  Incidentally, there is
in Standard.Character a Greek mu (position 181) precisely to be able to write SI
prefixes.

Would there be a way to provide conversion of units?  For instance you would say
that an inch is 2.54 cm and it would know how to convert square inches to square
centimeters.  That would be cool, but I don't know if it's possible.

****************************************************************

From: Tucker Taft
Sent: Monday, February 3, 2003  1:26 PM

Pascal Leroy wrote:
>
> > !subject Physical Units Checking
>
> I like it.
>
> Random comments:
>
> I wonder why you pass Names_Of_Dimensions and not an instantiation of
> Dimension_Exponents to System_Of_Units.  If you work with several system of
> units, it would seem that factoring the exponents could be useful.  (I am told
> that there are countries were SI is not the only system of units in practical
> use.)

The Dimension_Exponents package was really just to simplify the
visible part of System_Of_Units, so it didn't include the
operators for Exponent_Arrays.  I played around with using
a formal package parameter instead of the enumeration type for
the names of dimensions, but it seemed to add complexity for the
user without any obvious additional functionality.  You can
still factor out the exponents by having multiple instantiations
of System_Of_Units all use the same Names_Of_Dimensions type.
If some other abstraction really wanted a formal package instead
of the formal type, the instantiator has an instance of Dimension_Exponents
called "Dimensions" available in any instantiation of System_Of_Units.

> I think this should not be restricted to floating-point types.  Fixed-point
> types and complex come to mind.

I discussed this in the AI.  I fear it may make the interface a bit
daunting.  Perhaps if we provide pre-instantiations, that becomes
less of an issue.

>
> References to GNAT bugs have nothing to do in an AI, unless we want to enshrine
> them in the RM ;-)

I agree with that, but we presumably don't want to propose something
that can't be compiled by popular compilers, so I wanted to explain why I made Text_Output
a nested package.  That could presumably be relegated to the discussion
section only, or the ACT folks could sign up to fix the problem
real-soon-now.

>
> The style of Text_Output, where you pass Fore/Aft/Exp at instantiation time, is
> different from that of, say, Complex_IO.  I don't think that's a good idea.

I suppose consistency make sense.  But having the Default_Fore, etc. as
global variables just seems gratuitously non thread-safe.  Perhaps the
generic parameters could be the Defaults, and then each Put function
(and Image) could take Fore/Aft/Exp as actual parameters.  Or perhaps
I could just accept the gnarly "standard" approach to Default_Fore/Aft/Exp
and get on with it... ;-).  I guess I still think the default ought
to be without an exponent.  The output is so much easier to read without
exponents flying around, and one of the points of units is so you don't
have to keep talking about 3.5E-9 meters, and instead can talk about 3.5 nm.

> A preinstantiation should be provided for the SI system.  Btw, some of the
> examples will need to be sanitized to be in line with the SI conventions (s
> instead of sec, K is for Kelvin, k is for kilo, etc.).  Incidentally, there is
> in Standard.Character a Greek mu (position 181) precisely to be able to write SI
> prefixes.

I will need some help with the SI preinstantiation.  Maybe Thom Brooke
can help do that.  I am definitely not an expert in SI.

> Would there be a way to provide conversion of units?  For instance you would say
> that an inch is 2.54 cm and it would know how to convert square inches to square
> centimeters.  That would be cool, but I don't know if it's possible.

That is already possible.  Any two units that have the same dimensions
can have a scaling function created for them by instantiating the Scaling
generic.  So if you build up square-cm and square-inch using Product_Unit
from cm and inch, and inch is defined in terms of cm using Scaled_Unit (or
vice-versa), then Scaling can be instantiated with square-cm and square-inch
and the Scale functions will do the right thing.   You don't
have to explicitly instantiate Scaling if the units are defined directly
in terms of each other using Scaled_Unit (since it pre-instantiates Scaling),
but if the units are only indirectly related, the Scaling generic may be used
to produce direct Scale converters.  See the attached "test_squares.ada" example.

Here is the output of running the attached example to get this kind of scaling:

  30.000 square-cms converts to 4.650 square-inches

----

with Ada.Text_IO;
with Ada.Units.system_of_units;
with Ada.Units.System_Of_Units.Text_Output;
use Ada.Units;
procedure test_squares is
    type dim_names is (length, mass, time);

    package cgs is new system_of_units(dim_names, Standard.Float);
    use type cgs.unitless.value;

    package cm is new cgs.unit("cm", (length => 1, others => 0));
    package cm_output is new cgs.text_output(cm.signature);

    package square_cm is new cgs.product_unit(cm.sig, cm.sig, name =>
      "square-cm");
    package sqcm_output is new cgs.text_output(square_cm.sig);

    package inch is new cgs.scaled_unit(cm.sig, relative_scale => 2.54,
      name => "inch");

    package square_inch is new cgs.product_unit(inch.sig, inch.sig, name =>
      "square-inch");
    package sqin_output is new cgs.text_output(square_inch.sig,
      plural => "square-inches");

    package square_scaling is new cgs.scaling(
      square_cm.sig, square_inch.sig);

    sqcm : square_cm.value := 30.0;

    sqi : square_inch.value := square_scaling.scale(sqcm);

begin

    Ada.Text_IO.Put_Line(sqcm_output.image(sqcm) & " converts to " &
      sqin_output.image(sqi));

end;

****************************************************************

From: Robert I. Eachus
Sent: Monday, February 3, 2003  1:47 PM

I need to do more reading of the proposal, but there is one comment I
would like to make up front.

There are some computations where you run into non-integer values for
units.  These have no meaning as such in the final results, but are
needed in intermediates.  I never tried to deal with exponentials and
logs, but half-integer powers show up all over the place.  The "trick" I
used to get around this was to use Paul Hilfinger's package with all the
units multiplied by two.  That way the 3/2 power of length had an
exponent of 3.  I recently commented to Ben Brogol on this that using a
multiplier of 6 might be worthwhile, or even 12.  Sixty, which allows
for square, cube, fourth and fifth roots looks very execessive.  But
doing it this way allowed for a meaningful square root function for
values with units.

Of course, it would be nicest if the exponent values could be hiden from
sight.  In other words:

type Meter is private;

but then you lose the visible numeric literals.  Of course that is not
necessarily a bad thing:

X: Centimeters := 1.0 * Cm;

****************************************************************

From: Tucker Taft
Sent: Monday, February 3, 2003  2:21 PM

My initial version of this used floats for the exponents, and that
worked fine (they aren't discriminants, so there is really no
problem supporting floats as exponents).  My co-sponsor didn't
think it was necessary to accommodate non-integral exponents,
so I switched to integers.  But it could easily be switched back.
Of course we would then probably want a version of Sqrt that would
do the "right thing" with respect to units, which is quite
possible.  Also, a Square_Root_Unit generic would be desirable.

Note that exponents are "fiddled with" only at generic instantiation
time, so overhead relating to using floating point exponents would
be irrelevant.

> Of course, it would be nicest if the exponent values could be hiden from
> sight.  In other words:
>
> type Meter is private;

The visibility of the exponents has *nothing* to do with the
privateness of the type in this proposal.  You probably need
to take another look at it...

> but then you lose the visible numeric literals.  Of course that is not
> necessarily a bad thing:
>
> X: Centimeters := 1.0 * Cm;

I discussed the issue of privateness in the AI.  More specific comments on
this topic are welcome.

****************************************************************

From: Robert A. Duff
Sent: Monday, February 3, 2003  2:42 PM

> My initial version of this used floats for the exponents, and that
> worked fine (they aren't discriminants, so there is really no
> problem supporting floats as exponents).

I would think the exponents should be fixed point.  If you want integer
exponents, you use 'Small=1.0.  If you want square roots, you use
'Small=0.5.  Or whatever else you want.

****************************************************************

From: Gary Dismukes
Sent: Monday, February 3, 2003  2:14 PM

As Pascal pointed out:
>
> References to GNAT bugs have nothing to do in an AI, unless we want to enshrine
> them in the RM ;-)

Yes, please make sure that the two references to GNAT in the nested
generic package Text_Output (both in the spec and the later sample
body) are excised in the first version of this AI that's officially
posted to the web site.

-- Gary (on behalf of Ada Core Technologies:)

****************************************************************

From: Tucker Taft
Sent: Monday, February 3, 2003  3:29 PM

Well, at least I got your attention ;-).

****************************************************************

From: Gary Dismukes
Sent: Monday, February 3, 2003  4:10 PM

Probably the best thing to do in a case like this is to send
a problem report to report@gnat.com with a complete test case.
It seems reasonable to assume that in a case of legal Ada like
this that compilers will (and should) get fixed to allow it.

Anyway, at least now we know about it. ;-)

****************************************************************

From: Pascal Leroy
Sent: Tuesday, February 4, 2003  3:08 AM

> > The style of Text_Output, where you pass Fore/Aft/Exp at instantiation
> > time, is different from that of, say, Complex_IO.  I don't think that's
> > a good idea.
>
> I suppose consistency make sense.  But having the Default_Fore, etc. as
> global variables just seems gratuitously non thread-safe.  Perhaps the
> generic parameters could be the Defaults, and then each Put function
> (and Image) could take Fore/Aft/Exp as actual parameters.  Or perhaps
> I could just accept the gnarly "standard" approach to Default_Fore/Aft/Exp
> and get on with it... ;-).  I guess I still think the default ought
> to be without an exponent.  The output is so much easier to read without
> exponents flying around, and one of the points of units is so you don't
> have to keep talking about 3.5E-9 meters, and instead can talk about 3.5 nm.

Fine.  What I am suggesting is to use the style of Text_IO, but use 0 for the
default exponent.  That's exactly what Text_IO.Decimal_IO does, actually (I
suppose that accountants are not too happy with numbers like 1.35E5 USD).

> I will need some help with the SI preinstantiation.  Maybe Thom Brooke
> can help do that.  I am definitely not an expert in SI.

I am not an expert either, but having had some scientific education in a country
where SI is a religious dogma I know the basics.  I think I can help with the
preinstantiation.

> Here is the output of running the attached example to get this kind of
scaling:
>
>   30.000 square-cms converts to 4.650 square-inches

Great.

****************************************************************

From: Christoph Grein
Sent: Tuesday, February 4, 2003  4:00 AM

!subject Physical Units Checking

Jean-Pierre Rosen sent me this new AI to comment because I've prepared a paper
for Ada-Europe 2003 giving a survey of physical units handling techniques in
Ada.

While the proposal includes a lot of clever ideas about using generics with
(generic) signature packages, I must say that on first sight, I'm not very
impressed by the outcome. The proposed method is aimed at compile-time
checking of dimensions, but it suffers like all the members of this family
from the combinatorial explosion of instantiations and overloaded operators.
And despite all these overloadings, it remains silent how equations like

   d = (1/2) g t^2

should be handled when solved for t, because powers and roots are not
included.

This may sound like a very harsh wording, and I will put some flesh around
the bones of my critique presently.

Please do not misunderstand me. I do not want to say that there are no merits
in this proposal. I only think it is not a good idea to standardise it and
say: "Look, this is the way you have to deal in Ada with dimensions."

Let me rephrase Robert Dewar, who, referring to proposals for standard
container libraries, said:

   Put the proposal on the net, make it public, and wait for the
   reaction. If the proposal finds support, you can later think
   about standardisation.

Let me start with nit-picking and then go on to the more severe
deficiencies as I see them.

1. Names

Names of units (especially SI) are case sensitive.
The currently defined prefixes are the following:

  yocto: constant := 1.0E-24;  -- y
  zepto: constant := 1.0E-21;  -- z
  atto : constant := 1.0E-18;  -- a
  femto: constant := 1.0E-15;  -- f
  pico : constant := 1.0E-12;  -- p
  nano : constant := 1.0E-09;  -- n
  micro: constant := 1.0E-06;  --  (u)
  milli: constant := 1.0E-03;  -- m
  centi: constant := 1.0E-02;  -- c
  deci : constant := 1.0E-01;  -- d
  deka : constant := 1.0E+01;  -- da
  hecto: constant := 1.0E+02;  -- h
  kilo : constant := 1.0E+03;  -- k
  mega : constant := 1.0E+06;  -- M
  giga : constant := 1.0E+09;  -- G
  tera : constant := 1.0E+12;  -- T
  peta : constant := 1.0E+15;  -- P
  exa  : constant := 1.0E+18;  -- E
  zetta: constant := 1.0E+21;  -- Z
  yotta: constant := 1.0E+24;  -- Y

It further uses the fancy name "heca" for "hecto" and gets some prefix
symbols wrong:

"deca"  is 'da' not 'D'
"hecto" is 'h'  not 'H'
"kilo"  is 'k'  not 'K'

Also the symbol for Second is 's', not "sec". Speed is measured in
"m/s" not "m/sec".

See the National Institute of Standards and Technology
http://physics.nist.gov/cuu/Units/

This of course is nit-picking and can easily be cured.

2. Product Unit Name

Why is the default name of a product unit Unit_a & '-' & Unit_B?
Is this a misprint? Unit_a & '*' & Unit_B seems more appropriate and
is in line with the quotient name Unit_a & '/' & Unit_B.

   J = N-m  vs.  J = N*m

3. Mathematical Functions

These are not included. There is a statement:

   "For instance it is not clear it makes sense to instantiate
   Generic_Elementary_Functions with a type representing
   centimeters."

It is absolutely clear that it does not at all make sense.

exp (5 cm), ln (5 cm), sin (5 cm) all are nonesense.

However sqrt (5 cm) makes perfectly sense as does sin (5 cm, cycle => 10 cm)
and tan (y => 10 cm, x => 5 cm).

So mathematics has to be included up front, not added later.

4. Exponentiation and Roots

This is the most prominent shortcoming. How do you want to deal with the
most simple mechanical equation cited above

   d = (1/2) g t^2

let along with the Stefan Boltzmann equation

   S = Sigma * T^4

[S] = W/m^2, or the Schottky-Langmuir equation

   j = (4/9) eps0 Sqrt (2 e0 / m0) U^(3/2) / d^2

[j] = A/m^2? When you solve these equations for t, T, U, you need roots.
And Schottky-Langmuir shows that you even need fractional powers.

While in SI, final results never have fractional powers, intermediate
results do, and you want to be able to rearrange items in equations as is
most appropriate to the problem at hand and do not want to be forced to
write just the way the method supports.

   U^(3/2) => Sqrt (U * U * U)  -- what a mess

   j^(2/3) => ???

5. Combinatorial Explosion

As the examples above show, this method suffers severly from the
combinatorial explosion of operations needed.

Up to which power do you want to go? At least the fourth power seems
reasonable and for the inverse you then need square roots, cubic roots,
fourth roots.

If you do 3D vector dynamics, calculation of the absolute value of an
acceleration vector needs time in the (negative) fourth power.

Physics with all its powers and roots evades these (I'm apt to say:
naive) attempts.

The example code uses just three dimensions, Length, Time, Mass. In such
a restricted world, the proposal might work (but see below), but I doubt
that you can make it work for all seven SI base units.

6. Unit Systems

As an example, the CGS system is used, however only in a very restricted
form based on just cm, g, s. If you want to include electricity, you
immediately run into severe problems with this proposal.

The most appropriate CSG system is the Gaussian one (outdated as any other
CSG from practical use): It is symmetric in electric and magnetic effects,
i.e. Maxwell's equations are symmetric in vacuum and corresponding electric
and magnetic items have the same dimensions (albeit carrying different
names); there is just one fundamental constant, the speed of light.

The problem is that the basic unit for electric charge has fractional
powers:

   1 esu = 1 cm^(3/2) g^(1/2) s^(-1)

How are you going to handle this?

7. Clumsiness

Basing everything on a set of generics is appealing from a theoretic point
of view, but if I, as a developer, am given this set, it seems very awkward
for me to set up the system with the dimensions I need.

What developers want, is a simple set of ready-to-use packages. Until I see
an example with more than just three dimensions (best would be the full
SI system) where I can write

   U := cuberoot (Whatever);  -- [U] = V

as for Schottky-Langmuir above, without being artificially forced to arrange
items in Whatever in a form the current instance can handle, I'm not
convinced that this proposal is ripe for standardisation.

8. Abstract Operators

An annoying feature is that illegal operators like time * time -> time are
present and made abstract, rather than being absent.

Abstractness does not prevent operators to be taken into account upon
overload resolution. So they may raise their ugly heads every now and then
to the dismay of the poor user of the proposal (see below).

9. Over-Ambitiousness

While being severely flawed in some aspects, the proposal is over-ambitious
in wanting to provide natural language names for the units.

   27.000 meters + 540.000 cm = 3240.000 cm

Expecting such an output is silly. What one _can_ expect is

   27.000 m + 540.000 cm = 3240.000 cm

Natural language output like the following is out of the due purpose of a
package for unit handling:

   If traveled in 33.000 secs then speed = 98.182 cm/sec

In Italian, kilogram is chilogramm, you buy "un etto" or "due etti" of
something, i.e. 1 or 2 hg (hectogram, not Hg, "Hecagramm" - Hg is
quicksilver).

Do you really want to go into this business of internationalising plurals?

10. Example

I have added an equation that is quite common:

   M = m0 * (T/Tau)^2

and written it in several forms. The result is below. Many of the failing
statements can be cured be adding yet more instantiations. This is what I
mean with combinatorial explosion. Even for only three dimensions, this is
becoming awkward.

And if you write it in the most natural way, abstract operations get into
your way.

  use type kg.value, sec.value;

  T, Tau: sec.value := 1.0;
  M, m0 : kg .value := 1.0;

begin

  M := m0 * (T*T/(Tau*Tau));
  --          1      2
  -- >>> cannot call abstract subprogram ""*""
  -- >>> cannot call abstract subprogram ""*""

  M := m0 * (T*T)/(Tau*Tau);
  --      1           4
  -- >>> invalid operand types for operator "*"
  -- >>> left operand has type "Ada.Units.System_Of_Units.Value" from instance at line 28
  -- >>> right operand has type "Ada.Units.System_Of_Units.Value" from instance at line 19
  -- >>> expected type "Ada.Units.System_Of_Units.Value" from instance at line 28
  -- >>> found type "Ada.Units.System_Of_Units.Value" from instance at line 19

  M := m0 * T*T/(Tau*Tau);
  --      1   4     6
  -- >>> invalid operand types for operator "*"
  -- >>> left operand has type "Ada.Units.System_Of_Units.Value" from instance at line 28
  -- >>> right operand has type "Ada.Units.System_Of_Units.Value" from instance at line 19
  -- >>> expected type "Ada.Units.System_Of_Units.Value" from instance at line 28
  -- >>> found type "Ada.Units.System_Of_Units.Value" from instance at line 19
  -- >>> expected type "Ada.Units.System_Of_Units.Value" from instance at line 28
  -- >>> found type "Ada.Units.System_Of_Units.Value" from instance at line 19

  M := m0 * (T/Tau)*(T/Tau);

  M := m0 * (T/Tau*T/Tau);
  --              |
  -- >>> ambiguous expression (cannot resolve ""*"")
  -- >>> possible interpretation at a-usofun.ads:94, instance at line 19

  M := m0 * T/Tau*T/Tau;
  --      1   4   6 8
  -- >>> invalid operand types for operator "*"
  -- >>> left operand has type "Ada.Units.System_Of_Units.Value" from instance at line 28
  -- >>> right operand has type "Ada.Units.System_Of_Units.Value" from instance at line 19
  -- >>> expected type "Ada.Units.System_Of_Units.Value" from instance at line 28
  -- >>> found type "Ada.Units.System_Of_Units.Value" from instance at line 19
  -- >>> expected type "Ada.Units.System_Of_Units.Value" from instance at line 28
  -- >>> found type "Ada.Units.System_Of_Units.Value" from instance at line 19
  -- >>> expected type "Ada.Units.System_Of_Units.Value" from instance at line 28
  -- >>> found type "Ada.Units.System_Of_Units.Value" from instance at line 19

  M := m0 * T**2/Tau**2;
  --      1         4
  -- >>> invalid operand types for operator "*"
  -- >>> left operand has type "Ada.Units.System_Of_Units.Value" from instance at line 28
  -- >>> right operand has type "Ada.Units.System_Of_Units.Value" from instance at line 19
  -- >>> expected type "Ada.Units.System_Of_Units.Value" from instance at line 28
  -- >>> found type "Ada.Units.System_Of_Units.Value" from instance at line 19

  M := m0 * (T/Tau)**2;

end test_units;
--------------------------------------------------------------------------
Please let me summarize.

I do _not_ say that the method does not have its merits. I only want to point
out that it is, in its present form, not fit to handle physics in its whole
generality - which would be implied if it were standardised.

And, by the way, it wasn't the wrong equations that made Mars Lander fail,
it was improper mixing of imperial and metric units in communicating CSCIs,
and this method, as any other method, would not have prevented this failure,
since units are not transferred via data buses nor global memory.

Until shown a convincing example of a compile-time solution, I do believe
that full dimension handling can only be done during run-time (in Ada as it
is now).

C++ with its implicit instantiation of templates can do during compile-time
what Ada can do only during run-time. And indeed, there was a proposal of
such a feature for Ada9X:

   J. Shen, G. V. Cormack, Automatic Instantiation in Ada,
   ACM 0-89791-445-7/91/1000-0338 $1.50

(I do not say that I think inclusion of implicit instantiation into Ada is a
sensible thing. I let decide upon this by more competent persons.)

****************************************************************

From: Pascal Leroy
Sent: Tuesday, February 4, 2003  6:39 AM

> 8. Abstract Operators
>
> An annoying feature is that illegal operators like time * time -> time are
> present and made abstract, rather than being absent.
>
> Abstractness does not prevent operators to be taken into account upon
> overload resolution. So they may raise their ugly heads every now and then
> to the dismay of the poor user of the proposal (see below).

Note that AI 310 proposes a mechanism to ignore abstract operations during
overload resolutions in some cases.  This would help a lot in the context of
this AI.  In fact, I don't believe that an appropriate static solution to the
issue of physical unit checking can be found without AI 310 (or something
similar).

****************************************************************

From: David C. Hoos, Sr.
Sent: Tuesday, February 4, 2003  7:10 AM

> However sqrt (5 cm) makes perfectly sense as does sin (5 cm, cycle => 10 cm)
> and tan (y => 10 cm, x => 5 cm).

Did you mean arctan (y => 10 cm, x => 5 cm)?

****************************************************************

From: Christoph Grein
Sent: Tuesday, February 4, 2003  7:22 AM

Yes, of course. Thanx for reading so well.

****************************************************************

From: Pascal Leroy
Sent: Tuesday, February 4, 2003  7:11 AM

> 2. Product Unit Name
>
> Why is the default name of a product unit Unit_a & '-' & Unit_B?
> Is this a misprint? Unit_a & '*' & Unit_B seems more appropriate and
> is in line with the quotient name Unit_a & '/' & Unit_B.
>
>    J = N-m  vs.  J = N*m

Actually, the character at position 183 is a middle dot which appears to be the
recommended way to express products of units (see
http://www.bipm.fr/pdf/si-brochure.pdf, paragraph 5.3).

****************************************************************

From: Tucker Taft
Sent: Tuesday, February 4, 2003  3:57 PM

Pascal Leroy wrote:
>
> > 8. Abstract Operators
> >
> > An annoying feature is that illegal operators like time * time -> time are
> > present and made abstract, rather than being absent.
> >
> > Abstractness does not prevent operators to be taken into account upon
> > overload resolution. So they may raise their ugly heads every now and then
> > to the dismay of the poor user of the proposal (see below).
>
> Note that AI 310 proposes a mechanism to ignore abstract operations during
> overload resolutions in some cases.  This would help a lot in the context of
> this AI.  In fact, I don't believe that an appropriate static solution to the
> issue of physical unit checking can be found without AI 310 (or something
> similar).

Actually, I think my attempt to use visible floating point types
is doomed, so I am in the process of changing to using private types
except for unitless/dimensionless types.
This will eliminate the need for making any predefined operators abstract.

The reason why using visible floating point types is doomed is that
numeric literals and named numbers will be usable anywhere and everywhere,
either creating lots of ambiguity, or "nicely" allowing the user to
violate the units checking model they so carefully constructed.

So my plan is to add "Val" and "Mag" (for "Magnitude") functions as follows:

   function Val(Mag : Unitless.Value) return My_Unit.Value;
   function Mag(Val : My_Unit.Value) return Unitless.Value;

These are analogous to the 'Val and 'Pos attributes of enumeration types.
To create a literal of a given unit, you can write "My_Unit.Val(3.325)".
If for some reason you want the underlying magnitude of a "My_Unit" value,
you can do something like use "My_Unit.Mag(X)".  Of course, the operators
defined by the various _Unit packages will use the "Mag()" and
"Val()" functions extensively to convert to/from a "real" floating
point type.

So, stay tuned for the next version...

****************************************************************


Questions? Ask the ACAA Technical Agent