--- ais/ai-00324.txt 2003/02/05 21:45:39 1.1 +++ ais/ai-00324.txt 2003/07/26 03:26:02 1.2 @@ -1908,3 +1908,3107 @@ **************************************************************** +From: Christoph Grein +Sent: Wednesday, February 5, 2003 12:01 AM + +> 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. + +So why don't you use + + function Pos(Val : My_Unit.Value) return Unitless.Value; + +> To create a literal of a given unit, you can write "My_Unit.Val(3.325)". + +People are sometimes peculiar. I used the unary plus to construct private type +values from literals. Many collegues could never get used to this and kept +complaining. So in a follow-on project (where I'm not part) they changed my +private types to open numeric types just to have literals and with this opened +the door to many other unwanted features (like wrong type conversions). + +So how much would they object to My_Unit.Val(3.325)? + +Notwithstanding my above comments, I would propose to use + + function "+"(Mag : Unitless.Value) return My_Unit .Value; + function Mag(Val : My_Unit .Value) return Unitless.Value; + +> 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... + +Will this also take care of some of my other comments? + +**************************************************************** + +From: Tucker Taft +Sent: Wednesday, February 5, 2003 3:49 AM + +> So why don't you use +> +> function Pos(Val : My_Unit.Value) return Unitless.Value; + +Well "Pos" stands for position number, and it seemed +like that really only made sense for discrete types. + + +... +> Notwithstanding my above comments, I would propose to use +> +> function "+"(Mag : Unitless.Value) return My_Unit .Value; +> function Mag(Val : My_Unit .Value) return Unitless.Value; + +I think using an opeator for "Val" will get back to the +same old problem. If you write "Y := (+ 33.3) * X" then +you have said nothing about the units of "33.3" and the +compiler will automatically pick something that works. +That seems to pretty much defeat any units checking, +since you can assign anything to anything if you just +multiply it by "(+1.0)". + +We could define a parameterless version of "Val" which +was equivalent to Val(1.0), so you could write: + + "Len := 3.0 * cm.Val;" rather than "Len := cm.Val(3.0);" + +I don't see that as being any better, but maybe it +looks nicer with the literal outside of parens. +Or we could call this parameterless function "Unit" giving + + "Len := 3.0 * cm.Unit;" + +Of course now the term "Unit" is getting pretty heavily overloaded, +but maybe that is OK. + +... +> Will this also take care of some of my other comments? + +Yes, I hope it will address a number of them. +Thanks a lot for your careful review. + +**************************************************************** + +From: Christoph Grein +Sent: Wednesday, February 5, 2003 4:26 AM + +> "Len := 3.0 * cm.Val;" rather than "Len := cm.Val(3.0);" +> +> Or we could call this parameterless function "Unit" giving +> +> "Len := 3.0 * cm.Unit;" + +I like this much more since it looks natural: D = 5 cm. + +This is what I did in my package - I have constants for each every SI unit: + + Watt: constant Item := Joule / Second; + + Power: Item := 10.0 * Mega * Watt; + +**************************************************************** + +From: Tucker Taft +Sent: Wednesday, February 5, 2003 5:39 AM + +Ok, I have added a function called Unit, which +is just a renaming of "Val" but with the parameter defaulted to +1.0. I.e.: + + function Unit(Mag : Scale_Type := 1.0) return Value renames Val; + +I have included an example using some of the +SI preinstantiations below, along with this new +"Unit" function. + +----------- example of output ----------- + + 27.000 m + 540.000 cm = 3240.000 cm + If traveled in 33.000 s then speed = 98.182 cm/s + Three kilograms = 3.000 kg + Two and a half microsecs = 2.500 us + +----------- source code for example using SI_Units ---------- + +with Ada.Text_IO; +with Ada.Units.system_of_units; +with Ada.Units.SI_Units.Derived_Units; + +use Ada.Units; +use Ada.Units.SI_Units; +use Ada.Units.SI_Units.Derived_Units; +procedure test_si_units is + + package cm is new SI.scaled_unit(Meter.Sig, Relative_Scale => 0.01); + package cm_output is new SI.text_output(cm.signature); + + package g_output is new SI.text_output(Gram.sig); + + package sec renames SI_Units.Second; + package sec_output is new SI.text_output(sec.signature); + + package cps is new SI.quotient_unit(cm.sig, sec.sig); + package cps_output is new SI.text_output(cps.signature); + + package meter_output is new SI.text_output(meter.sig); + + package kg renames SI_Units.Kilogram; + package kg_output is new SI.text_output(kg.sig); + + package usec is new SI.scaled_unit( + base_unit => sec.sig, relative_scale => 1.0E-6); + package usec_output is new SI.text_output(usec.sig); + + package msec is new SI.scaled_unit(base_unit => sec.sig, + relative_scale => 1.0E-3); + + use type cm.value, meter.value; + X : meter.value := 27.0 * meter.unit; + + Y : cm.value := cm.val(540.0); + + + use type sec.value; + + Speed : cps.value; -- cm/sec + Interval : sec.value := 33.0 * sec.unit; -- seconds + + use type cps.value; + use type usec.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(kg.val(3.0))); + + ada.text_io.put_line("Two and a half microsecs = " & + usec_output.image(2.5 * usec.unit)); +end; + +**************************************************************** + +From: Tucker Taft +Sent: Wednesday, February 5, 2003 5:19 AM + +Here are updated specs and bodies for the Ada.Units.* +packages and generics. I haven't updated +the AI as a whole yet. + +The major changes are: + +- the units are all now based on private types + except for dimensionless, scale=1.0 units. + This prevents the direct use of literals + except as unitless multipliers. A "Val" + function, analogous the the 'Val attribute + of enumeration types, is available for constructing + values with particular units. E.g. "cm.Val(3.5)" + constructs a value of type "cm.Value" with magnitude + 3.5. You can get back the magnitude with the + inverse function, called "Mag" (analogous to the + 'Pos attribute of enumeration types). + +- there is now support for units that are integral + or fractional powers of one another. There is + special support for squared, cubed, and square + root units, and then basic support for arbitrary + powers. This required returning to floating-point + exponents in the exponent-array. Since exponent-array + fiddling all happens at elaboration time, the additional + overhead seems pretty minor. Note that we are relying + on well-behaved floating point (e.g. IEEE) so equality + works reliably with fractional exponents. If this + proves to be a problem, we may have to change the assertions + to allow for a small mismatch in the exponents (based on + Model_Epsilon, presumably). + +- The scaling prefixes used now conform to + the SI standard, except for places where they require + characters outside of the ISO 646 7-bit set (e.g. "u" + rather than "<greek mu>"). The prefixes can be + overridden via the Localization mechanism (see below). + +- The automatic unit name construction support package now + allows for localization (aka Internationalization), in + simple ways by simply changing certain fields of + the Localization record, and in more sophisticated ways + by extending the Localization type and overriding one or + more of the name construction operations. + +- There are now SI_Units and SI_Units.Derived_Units + packages that include the standard 7 SI units, and 21 + of the 22 standard "derived" SI units. These use + the standard 1-3 character names for the units, + except in cases where characters outside of the + 7-bit ISO 646 set were needed (e.g. "ohm" is used + instead of "<omega char>"). The names can be + overridden when instantiating Text_Output. + +- The Text_Output generic now more closely conforms + to the existing IO packages, such as Float_IO, + Decimal_IO, and Complex_IO, in its handling of Fore/Aft/Exp. + + + +-------------- specs ------------- + +pragma Ada_Child; +package Ada.Units is + type Exponent_Type is digits 5; -- implementation defined + type Scale_Type is digits 11; -- Implementation defined +end Ada.Units; + +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 "-" + + function "*"(Left : Exponent_Type; Right : Exponent_Array) + return Exponent_Array; + function "*"(Left : Exponent_Array; Right : Exponent_Type) + return Exponent_Array; + -- Vector multiply +end Ada.Units.Dimension_Exponents; + +package Ada.Units.Name_Support is + -- Package of string utilities useful for generating + -- names automatically + + type Localization is tagged record + -- This type provides information that + -- controls the effect of the Name Support operations. + -- It may be extended, and the operations may be overridden + -- to provide more extensive localization. + Product_Name_Separator : Character; + -- Separator character to use to form a "product" unit + Quotient_Name_Separator : Character; + -- Separator character to use to form a "quotient" unit + Power_Name_Separator : Character; + Micro_Abbreviation : Character; + -- Character to use to represent "micro" + Max_Length_With_Short_Prefix : Natural; + -- Maximum length of unit name that should use + -- the "short" SI prefix; Set to Natural'Last + -- to use short prefix with all unit names. + Max_Length_With_No_Pluralization : Natural; + -- Maximum length of unit name that should + -- undergo no pluralization; Set to Natural'Last + -- to suppress all pluralization. + Pluralization_Character : Character; + -- Pluralization character to be used for names + -- longer than Max_Length_With_No_Pluralization. + end record; + + function Scale_Prefix(Locale : Localization; + 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) & Locale.Product_Name_Separator + + function Short_Scale_Prefix(Locale : Localization; + 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 + -- Scale_Type'Image(Scale_Factor) & Locale.Product_Name_Separator + + function Prefixed_Name(Locale : Localization; + 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 <= Locale.Max_Length_With_Short_Prefix chars in length, + -- "kilo" otherwise) + + function Pluralize(Locale : Localization; + Singular_Name : String) return String; + -- Return plural form given singular form. + -- In general returns Singular_Name unchanged + -- if it is <= Locale.Max_Length_With_No_Pluralization characters, + -- and adds a Locale.Pluralization_Character otherwise + + function Product_Name(Locale : Localization; + Unit_A, Unit_B : String) return String; + -- Return default name for Product_Unit, given name + -- of two units forming the product. + -- By default returns Unit_A & Locale.Product_Name_Separator & Unit_B + + function Quotient_Name(Locale : Localization; + Numerator, Denominator : String) return String; + -- Return default name for Quotient_Unit, given name + -- of numerator and denominator + -- By default returns Numerator & Locale.Quotient_Name_Separator & + -- Denominator + + function Is_Quotient_Name(Locale : Localization; + Name : String) return Boolean; + -- Return true if name consists of numerator and denominator + -- with separating Locale.Quotient_Name_Separator + + function Numerator_Part(Locale : Localization; + Quotient : String) return String; + -- Return numerator part of name constructed via + -- Quotient_Name + + function Denominator_Part(Locale : Localization; + Quotient : String) return String; + -- Return denominator part of name constructed via + -- Quotient_Name + + function Power_Name(Locale : Localization; + Base_Unit : String; Power : Exponent_Type) return String; + -- Return default name for Base_Unit raised to a power. + -- By default returns Base_Unit & Locale.Power_Name_Separator & + -- decimal textual image for Power, parenthesized if negative, with no + -- leading space if positive, and no decimal point if integral + + -- Now that all dispatching operations have been declared, + -- we can declare a default locale to use for System_Of_Units + Default_Locale : constant Localization := + -- Defaults for localization, appropriate to + -- ISO 646, English-speaking usages. + (Product_Name_Separator => '.', + Quotient_Name_Separator => '/', + Power_Name_Separator => '^', + Micro_Abbreviation => 'u', + Max_Length_With_Short_Prefix => 3, + Max_Length_With_No_Pluralization => 3, + Pluralization_Character => 's'); + +end Ada.Units.Name_Support; + +package Ada.Units.Powers is + -- Functions used to support powers and fractional units + function Sqrt(Val : Scale_Type) return Scale_Type; + function Cube_Root(Val : Scale_Type) return Scale_Type; + function "**"(Left : Scale_Type; Right : Exponent_Type) return Scale_Type; +end Ada.Units.Powers; + +with Ada.Text_IO; -- for text_output +with Ada.Units.Name_Support; +with Ada.Units.Dimension_Exponents; +with Ada.Units.Powers; +generic + type Names_Of_Dimensions is (<>); + type Value_Type is digits <>; + Locale : Name_Support.Localization'Class := Name_Support.Default_Locale; +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. + -- Locale allows for more control over automatically + -- constructed unit names (see Ada.Units.Name_Support). + + 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 private; + with function Val(Mag : Scale_Type) return Value is <>; + with function Mag(Val : Value) return Scale_Type is <>; + 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. + -- Val and Mag (short for "Magnitude") are functions for + -- converting the private Value type to/from the Scale_Type. + + + 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.0); + Scale_Factor : constant Scale_Type := 1.0; + + function Val(Mag : Scale_Type) return Value; + function Mag(Val : Value) return Scale_Type; + + package Signature is new Unit_Signature( + Name, Exponents, Scale_Factor, Value); + + package Sig renames Signature; -- for convenience + + pragma Inline(Val, Mag); + end Unitless; + + package Private_Numeric is + -- Define a private type to be used as a base for further derivations; + -- provide additive operators that operate on the type; + -- provide multiplicative operators that combine with the unitless type. + + type Value is private; + + function "+"(Right : Value) return Value; + function "+"(Left, Right : Value) return Value; + + function "-"(Right : Value) return Value; + function "-"(Left, Right : Value) return Value; + + function "*"(Left : Value; Right : Unitless.Value) return Value; + function "*"(Left : Unitless.Value; Right : Value) return Value; + + function "/"(Left : Value; Right : Unitless.Value) return Value; + function "/"(Left : Value; Right : Value) return Unitless.Value; + + -- Provide operations for converting between private + -- type and visible floating point type + function Val(Mag : Scale_Type) return Value; + function Mag(Val : Value) return Scale_Type; + + private + pragma Inline("*", "/", Val, Mag); + + -- Use double rename "trick" to define "+"/"-" via renaming-as-body + type Base_Value is new Value_Type'Base; + function Plus(Right : Base_Value) return Base_Value renames "+"; + function Plus(Left, Right : Base_Value) return Base_Value renames "+"; + function Minus(Right : Base_Value) return Base_Value renames "-"; + function Minus(Left, Right : Base_Value) return Base_Value renames "-"; + + type Value is new Base_Value; + function "+"(Right : Value) return Value renames Plus; + function "+"(Left, Right : Value) return Value renames Plus; + function "-"(Right : Value) return Value renames Minus; + function "-"(Left, Right : Value) return Value renames Minus; + + end Private_Numeric; + + + 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 Private_Numeric.Value; + -- 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(Locale, 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 Private_Numeric.Value; + -- 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(Locale, 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. "gm.sec") + + type Value is new Private_Numeric.Value; + -- 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(Locale, 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 Private_Numeric.Value; + -- 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 Unitless.Value; + -- 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; + + generic + with package Base_Unit is new Unit_Signature(<>); + Name : in String := + Name_Support.Power_Name(Locale, Base_Unit.Name, Power => 2.0); + package Squared_Unit is + -- Instantiate this package for a unit that is + -- the square of some other unit. + -- The Exponents of the new type are twice those of Base_Unit. + -- The Scale_Factor for the new type is the square of + -- the scale of Base_Unit. + -- Use Scaled_Unit to get other scalings. + -- The default Name is <base_unit>^2 (e.g. "cm^2") + + type Value is new Private_Numeric.Value; + -- Get ops with unitless type, etc. + + -- provide other parameters of signature + Scale_Factor : constant Scale_Type := Base_Unit.Scale_Factor ** 2; + + Exponents : constant Exponent_Array := 2.0 * Base_Unit.Exponents; + + package Signature is new Unit_Signature( + Name, Exponents, Scale_Factor, Value); + + package Sig renames Signature; -- for convenience + + -- Declare multiplicative operators + function "*"(Left : Base_Unit.Value; Right : Base_Unit.Value) + return Squared_Unit.Value; + function "/"(Left : Squared_Unit.Value; Right : Base_Unit.Value) + return Base_Unit.Value; + + -- Declare squaring and square root operations + function Square(Val : Base_Unit.Value) return Squared_Unit.Value; + + function Sqrt(Val : Squared_Unit.Value) return Base_Unit.Value; + + pragma Inline("*", "/", Square); + end Squared_Unit; + + generic + with package Base_Unit is new Unit_Signature(<>); + with package Squared_Base_Unit is new Unit_Signature(<>); + Name : in String := + Name_Support.Power_Name(Locale, Base_Unit.Name, Power => 3.0); + package Cubed_Unit is + -- Instantiate this package for a unit that is + -- the cube of some other unit. + -- Must provide unit created using Squared_Unit as additional + -- parameter, so proper multiplicative operators can be declared. + + -- The Exponents of the new type are three times those of Base_Unit. + -- The Scale_Factor for the new type is the cube of + -- the scale of Base_Unit. + -- Use Scaled_Unit to get other scalings. + -- The default Name is <base_unit>^3 (e.g. "cm^3") + + -- Make sure Base_Unit and Squared_Base_Unit are related + -- properly + pragma Assert(2.0 * Base_Unit.Exponents = Squared_Base_Unit.Exponents); + pragma Assert( + Base_Unit.Scale_Factor**2 = Squared_Base_Unit.Scale_Factor); + + type Value is new Private_Numeric.Value; + -- Get ops with unitless type, etc. + + -- provide other parameters of signature + Scale_Factor : constant Scale_Type := Base_Unit.Scale_Factor ** 3; + + Exponents : constant Exponent_Array := 3.0 * Base_Unit.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( + Base_Unit, Squared_Base_Unit, Cubed_Unit.Signature); + + -- Rename operators for direct visibility + function "*"(Left : Base_Unit.Value; Right : Squared_Base_Unit.Value) + return Cubed_Unit.Value renames Ops."*"; + function "*"(Left : Squared_Base_Unit.Value; Right : Base_Unit.Value) + return Cubed_Unit.Value renames Ops."*"; + function "/"(Left : Cubed_Unit.Value; Right : Base_Unit.Value) + return Squared_Base_Unit.Value renames Ops."/"; + function "/"(Left : Cubed_Unit.Value; Right : Squared_Base_Unit.Value) + return Base_Unit.Value renames Ops."/"; + + -- Declare cubing and cube root operations + function Cube(Val : Base_Unit.Value) return Cubed_Unit.Value; + + function Cube_Root(Val : Cubed_Unit.Value) return Base_Unit.Value; + + pragma Inline(Cube); + end Cubed_Unit; + + generic + with package Base_Unit is new Unit_Signature(<>); + Name : in String := + Name_Support.Power_Name(Locale, Base_Unit.Name, Power => 0.5); + package Square_Root_Unit is + -- Instantiate this package for a unit that is + -- the square root of some other unit. + -- The Exponents of the new type are half those of Base_Unit. + -- The Scale_Factor for the new type is the square root of + -- the scale of Base_Unit. + -- Use Scaled_Unit to get other scalings. + -- The default Name is <base_unit>^(1/2) (e.g. "cm^(1/2)") + + type Value is new Private_Numeric.Value; + -- Get ops with unitless type, etc. + + -- provide other parameters of signature + Scale_Factor : constant Scale_Type := + Powers.Sqrt(Base_Unit.Scale_Factor); + + Exponents : constant Exponent_Array := 2.0 * Base_Unit.Exponents; + + package Signature is new Unit_Signature( + Name, Exponents, Scale_Factor, Value); + + package Sig renames Signature; -- for convenience + + -- Declare multiplicative operators + function "*"(Left : Square_Root_Unit.Value; + Right : Square_Root_Unit.Value) return Base_Unit.Value; + function "/"(Left : Base_Unit.Value; Right : Square_Root_Unit.Value) + return Square_Root_Unit.Value; + + -- Declare squaring and square root operations + function Square(Val : Square_Root_Unit.Value) return Base_Unit.Value; + + function Sqrt(Val : Base_Unit.Value) return Square_Root_Unit.Value; + + pragma Inline("*", "/", Square); + end Square_Root_Unit; + + generic + with package Base_Unit is new Unit_Signature(<>); + Power : in Exponent_Type; + Name : in String := + Name_Support.Power_Name(Locale, Base_Unit.Name, Power => Power); + package Arbitrary_Power_Unit is + -- Instantiate this package for a unit that is + -- some power of some other unit (integral or fractional) + -- The Exponents of the new type are "Power" times those of Base_Unit + -- The Scale_Factor for the new type is the given power of + -- the scale of Base_Unit. + -- Use Scaled_Unit to get other scalings. + -- The default Name is <base_unit>^(<power>>) (e.g. "cm^(1/4)") + -- In name, "power" is expressed as ratio of small integers + -- if is multiple of 1/2, 1/3, or 1/4. + + type Value is new Private_Numeric.Value; + -- Get ops with unitless type, etc. + + use Ada.Units.Powers; -- for "**" operator on Scale_Type + + -- provide other parameters of signature + Scale_Factor : constant Scale_Type := + Base_Unit.Scale_Factor ** Power; + + Exponents : constant Exponent_Array := Power * Base_Unit.Exponents; + + package Signature is new Unit_Signature( + Name, Exponents, Scale_Factor, Value); + + package Sig renames Signature; -- for convenience + + -- Declare appropriate exponentiation operations + function Exponentiate_By_Power(Val : Base_Unit.Value) + return Arbitrary_Power_Unit.Value; + + function Exponentiate_By_Inverse_Power( + Val : Arbitrary_Power_Unit.Value) return Base_Unit.Value; + + -- Instantiate Multiplicative_Operators to + -- get any appropriate "*" and "/" operators + + end Arbitrary_Power_Unit; + + + + -- NOTE: Text_Output would preferably be a generic child unit, + -- but some compilers can't deal with a generic child + -- that takes a signature declared in the generic parent. + generic + with package Unit_For_Output is new Unit_Signature(<>); + Singular : in String := Unit_For_Output.Name; + Plural : in String := Name_Support.Pluralize(Locale, Singular); + -- Name for unit, plural form + package 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 + + Default_Fore : Ada.Text_IO.Field := 2; + Default_Aft : Ada.Text_IO.Field := 3; -- 3 digits after decimal pt + Default_Exp : Ada.Text_IO.Field := 0; -- No exponent by default + + -- Default_Fore is minimum digits before decimal point + -- Default_Aft is digits after decimal point + -- Default_Exp is number of digits allowed for exponent + -- (as used in Float_IO.Put) + + + procedure Put(Val : in Unit_For_Output.Value; + Fore : Ada.Text_IO.Field := Default_Fore; + Aft : Ada.Text_IO.Field := Default_Aft; + Exp : Ada.Text_IO.Field := Default_Exp); + + procedure Put(File : in Ada.Text_IO.File_Type; + Val : in Unit_For_Output.Value; + Fore : Ada.Text_IO.Field := Default_Fore; + Aft : Ada.Text_IO.Field := Default_Aft; + Exp : Ada.Text_IO.Field := Default_Exp); + + function Image(Val : in Unit_For_Output.Value; + Fore : Ada.Text_IO.Field := Default_Fore; + Aft : Ada.Text_IO.Field := Default_Aft; + Exp : Ada.Text_IO.Field := Default_Exp) return String; + + end Text_Output; + +end Ada.Units.System_Of_Units; + +with Ada.Units.System_Of_Units; + pragma Elaborate_All(Ada.Units.System_Of_Units); +package Ada.Units.SI_Units is + -- This package provides a preinstantiation of System_Of_Units + -- for the International System of Units (SI), and + -- preinstantiations of the seven base units, plus + -- grams, which is used in defining kilograms. + + type SI_Dimensions is + (Length, Mass, Time, Electric_Current, + Temperature, Amount_Of_Substance, Luminous_Intensity); + package SI is new System_Of_Units(SI_Dimensions, Standard.Float); + + package Gram is new SI.Unit( + Exponents => (Mass => 1.0, others => 0.0), + Name => "g"); + + package Meter is new SI.Unit( + Exponents => (Length => 1.0, others => 0.0), + Name => "m"); + package Kilogram is new SI.Scaled_Unit( + Base_Unit => Gram.Sig, Relative_Scale => 1000.0); + package Second is new SI.Unit( + Exponents => (Time => 1.0, others => 0.0), + Name => "s"); + package Ampere is new SI.Unit( + Exponents => (Electric_Current => 1.0, others => 0.0), + Name => "A"); + package Kelvin is new SI.Unit( + Exponents => (Temperature => 1.0, others => 0.0), + Name => "K"); + package Mole is new SI.Unit( + Exponents => (Amount_Of_Substance => 1.0, others => 0.0), + Name => "mol"); + package Candela is new SI.Unit( + Exponents => (Luminous_Intensity => 1.0, others => 0.0), + Name => "cd"); + +end Ada.Units.SI_Units; + +package Ada.Units.SI_Units.Derived_Units is + -- This package provides preinstantiations for 21 of the + -- 22 standard named "derived units" of the SI system. + -- Centrigrade is omitted because it represents an + -- offset relative to Kelvin, which is not currently supported. + -- It also provides 6 other preinstantiations which + -- are either used in defining the standard derived units, + -- or expected to be of wide use. + + -- Various useful derived units + package Square_Meter is new SI.Squared_Unit(Meter.Sig); + + package Cubic_Meter is new SI.Cubed_Unit(Meter.Sig, Square_Meter.Sig); + + package Meter_Per_Second is new SI.Quotient_Unit(Meter.Sig, Second.Sig); + + package Second_Squared is new SI.Squared_Unit(Second.Sig); + + package Meter_Per_Second_Squared is new SI.Quotient_Unit( + Meter.Sig, Second_Squared.Sig); + + + -- 21 of 22 standard derived units + package Radian is new SI.Dimensionless_Unit(Name => "rad"); + + package Steradian is new SI.Dimensionless_Unit(Name => "sr"); + + package Hertz is new SI.Quotient_Unit( + SI.Unitless.Sig, Second.Sig, Name => "Hz"); + + package Newton is new SI.Product_Unit( + Kilogram.Sig, Meter_Per_Second_Squared.Sig, Name => "N"); + + package Pascal is new SI.Quotient_Unit( + Newton.Sig, Square_Meter.Sig, Name => "P"); + + package Joule is new SI.Product_Unit( + Newton.Sig, Meter.Sig, Name => "J"); + + package Watt is new SI.Quotient_Unit( + Joule.Sig, Second.Sig, Name => "W"); + + package Coulomb is new SI.Product_Unit( + Second.Sig, Ampere.Sig, Name => "C"); + + package Volt is new SI.Quotient_Unit( + Watt.Sig, Ampere.Sig, Name => "V"); + + package Farad is new SI.Quotient_Unit( + Coulomb.Sig, Volt.Sig, Name => "F"); + + package Ohm is new SI.Quotient_Unit( + Volt.Sig, Ampere.Sig, Name => "ohm"); + -- SI actually uses greek Omega, we are + -- sticking with ISO 646 (7-bit) characters here. + + package Siemens is new SI.Quotient_Unit( + Ampere.Sig, Volt.Sig, Name => "S"); + + package Weber is new SI.Product_Unit( + Volt.Sig, Second.Sig, Name => "Wb"); + + package Tesla is new SI.Quotient_Unit( + Weber.Sig, Square_Meter.Sig, Name => "T"); + + package Henry is new SI.Quotient_Unit( + Weber.Sig, Ampere.Sig, Name => "H"); + + package Lumen is new SI.Product_Unit( + Candela.Sig, Steradian.Sig, Name => "lm"); + + package Lux is new SI.Quotient_Unit( + Lumen.Sig, Square_Meter.Sig, Name => "lx"); + + package Becquerel is new SI.Quotient_Unit( + SI.Unitless.Sig, Second.Sig, Name => "Bq"); + + package Gray is new SI.Quotient_Unit( + Joule.Sig, Kilogram.Sig, Name => "Gy"); + + package Sievert is new SI.Quotient_Unit( + Joule.Sig, Kilogram.Sig, Name => "Sv"); + + package Katal is new SI.Quotient_Unit( + Mole.Sig, Second.Sig, Name => "kat"); + +end Ada.Units.SI_Units.Derived_Units; + +----------- bodies -------------- + +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 "-"; + + function "*"(Left : Exponent_Type; Right : Exponent_Array) + return Exponent_Array is + -- Vector multiply + Result : Exponent_Array; + begin + for I in Result'Range loop + Result(I) := Left * Right(I); + end loop; + return Result; + end "*"; + + function "*"(Left : Exponent_Array; Right : Exponent_Type) + return Exponent_Array is + -- Vector multiply + begin + -- Just reverse parameters and hand off to other multiply op + return Right * Left; + 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(Locale : Localization; + 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) & Locale.Product_Name_Separator + -- Note: these come from http://physics.nist.gov/cuu/Units/prefixes.html + begin + if Scale_Factor = 1.0 then + return ""; + elsif Scale_Factor = 10.0 then + return "deka"; + elsif Scale_Factor = 100.0 then + return "hecto"; + 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 = 1.0E18 then + return "exa"; + elsif Scale_Factor = 1.0E21 then + return "zetta"; + elsif Scale_Factor = 1.0E24 then + return "yotta"; + 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"; + elsif Scale_Factor = 1.0E-15 then + return "femto"; + elsif Scale_Factor = 1.0E-18 then + return "atto"; + elsif Scale_Factor = 1.0E-21 then + return "zepto"; + elsif Scale_Factor = 1.0E-24 then + return "yocto"; + else + return Scale_Type'Image(Scale_Factor) & + Locale.Product_Name_Separator; + end if; + end Scale_Prefix; + + function Short_Scale_Prefix(Locale : Localization; + 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) & Locale.Product_Name_Separator + -- Note: these come from http://physics.nist.gov/cuu/Units/prefixes.html + begin + if Scale_Factor = 1.0 then + return ""; + elsif Scale_Factor = 10.0 then + return "da"; + 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 = 1.0E18 then + return "E"; + elsif Scale_Factor = 1.0E21 then + return "Z"; + elsif Scale_Factor = 1.0E24 then + return "Y"; + 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"; + elsif Scale_Factor = 1.0E-15 then + return "f"; + elsif Scale_Factor = 1.0E-18 then + return "a"; + elsif Scale_Factor = 1.0E-21 then + return "z"; + elsif Scale_Factor = 1.0E-24 then + return "y"; + else + return Scale_Type'Image(Scale_Factor) & + Locale.Product_Name_Separator; + end if; + end Short_Scale_Prefix; + + function Prefixed_Name(Locale : Localization; + 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 <= Locale.Max_Length_With_Short_Prefix chars in length, + -- "kilo" otherwise) + begin + if Unit_Name'Length <= Locale.Max_Length_With_Short_Prefix then + return Short_Scale_Prefix(Locale, Relative_Scale) & Unit_Name; + else + return Scale_Prefix(Locale, Relative_Scale) & Unit_Name; + end if; + end Prefixed_Name; + + function Product_Name(Locale : Localization; + Unit_A, Unit_B : String) return String is + -- Return default name for Product_Unit, given name + -- of two units forming the product. + -- By default returns Unit_A & Locale.Product_Name_Separator & Unit_B + begin + return Unit_A & Locale.Product_Name_Separator & Unit_B; + end Product_Name; + + function Quotient_Name(Locale : Localization; + Numerator, Denominator : String) return String is + -- Return default name for Quotient_Unit, given name + -- of numerator and denominator + -- By default returns Numerator & Locale.Quotient_Name_Separator & + -- Denominator + begin + return Numerator & Locale.Quotient_Name_Separator & Denominator; + end Quotient_Name; + + function Is_Quotient_Name(Locale : Localization; + Name : String) return Boolean is + -- Return true if name consists of numerator and denominator + -- with separating Locale.Quotient_Name_Separator + begin + for I in Name'Range loop + if Name(I) = Locale.Quotient_Name_Separator then + return True; + end if; + end loop; + return False; + end Is_Quotient_Name; + + function Numerator_Part(Locale : Localization; + Quotient : String) return String is + -- Return numerator part of name constructed via + -- Quotient_Name + begin + for I in Quotient'Range loop + if Quotient(I) = Locale.Quotient_Name_Separator then + return Quotient(Quotient'First .. I-1); + end if; + end loop; + return Quotient; -- Should never happen + end Numerator_Part; + + function Denominator_Part(Locale : Localization; + Quotient : String) return String is + -- Return denominator part of name constructed via + -- Quotient_Name + begin + for I in Quotient'Range loop + if Quotient(I) = Locale.Quotient_Name_Separator then + return Quotient(I+1 .. Quotient'Last); + end if; + end loop; + return "??"; -- Should never happen + end Denominator_Part; + + function Pluralize(Locale : Localization; + Singular_Name : String) return String is + -- Return plural form given singular form. + -- In general returns Singular_Name unchanged + -- if it is <= Locale.Max_Length_With_No_Pluralization characters, + -- and adds a Locale.Pluralization_Character otherwise + begin + if Singular_Name'Length <= Locale.Max_Length_With_No_Pluralization then + -- Short unit names generally don't get pluralized + -- (e.g. "g"). + return Singular_Name; + elsif Is_Quotient_Name(Locale, Singular_Name) then + -- If is a quotient name, then pluralize the numerator + -- part only + return Quotient_Name(Locale, + Pluralize(Locale, Numerator_Part(Locale, Singular_Name)), + Denominator_Part(Locale, Singular_Name)); + elsif Locale.Pluralization_Character = 's' then + -- Special case the English rules + -- TBD: This is pretty naive at this point + case Singular_Name(Singular_Name'Last) is + when 'x' | 's' => + return Singular_Name & "es"; + when others => + return Singular_Name & 's'; + end case; + else + -- This presumes the language has a single character + -- universally used for plurals. + -- NOTE: We're not sure such a language exists. + -- Almost certainly, anyone serious about + -- making this work appropriately for another language + -- will want to extend Localization and override Pluralize + + return Singular_Name & Locale.Pluralization_Character; + end if; + end Pluralize; + + function Trimmed(Img : String) return String is + -- Local function to return image with leading space if any trimmed off + begin + if Img(Img'First) = ' ' then + return Img(Img'First+1..Img'Last); + else + return Img; + end if; + end Trimmed; + + function Power_Name(Locale : Localization; + Base_Unit : String; Power : Exponent_Type) return String is + -- Return default name for Base_Unit raised to a power. + -- By default returns Base_Unit & Locale.Power_Name_Separator & + -- rational image for Power, parenthesized if negative or + -- non integral, with no leading space if positive + Numerator : Exponent_Type := 0.0; + Int_Num : Integer; + begin + for Denominator in 1..4 loop + -- See whether is multiple of 1/1, 1/2, 1/3, or 1/4 + Numerator := Numerator + Power; -- Numerator = Den * Power + Int_Num := Integer(Numerator); + + if Exponent_Type(Int_Num) = Numerator then + -- Power is Integral multiple of 1/Denominator + declare + Num_Image : constant String := Trimmed(Integer'Image(Int_Num)); + begin + if Denominator /= 1 then + -- Parenthesize fractional power + return Base_Unit & Locale.Power_Name_Separator & + '(' & Num_Image & Locale.Quotient_Name_Separator & + Trimmed(Integer'Image(Denominator)) & ')'; + elsif Int_Num < 0 then + -- Parenthesize negative integral power + return Base_Unit & Locale.Power_Name_Separator & + '(' & Num_Image & ')'; + else + -- No paretheses necessary + -- since is a simple non-negative integral power + return Base_Unit & Locale.Power_Name_Separator & + Num_Image; + end if; + end; + end if; + end loop; + + -- Not an integral multiple of 1/1, 1/2, 1/3, or 1/4 + -- Just parenthesize image of power + return Base_Unit & Locale.Power_Name_Separator & + '(' & Trimmed(Exponent_Type'Image(Power)) & ')'; + end Power_Name; + +end Ada.Units.Name_Support; + +with Ada.Numerics.Long_Elementary_Functions; -- implementation-dependent choice +package body Ada.Units.Powers is + -- Functions used to support powers and fractional units + + subtype Elem_Func_Type is Standard.Long_Float'Base; + -- implementation-dependent choice + package Elementary_Functions + renames Ada.Numerics.Long_Elementary_Functions; + -- implementation-dependent choice + + function Sqrt(Val : Scale_Type) return Scale_Type is + begin + return Scale_Type(Elementary_Functions.Sqrt( + Elem_Func_Type(Val))); + end Sqrt; + + function "**"(Left : Scale_Type; Right : Exponent_Type) + return Scale_Type is + begin + return Scale_Type(Elementary_Functions."**"( + Elem_Func_Type(Left), Elem_Func_Type(Right))); + end "**"; + + function Cube_Root(Val : Scale_Type) return Scale_Type is + begin + return Val ** (1.0/3.0); + end Cube_Root; +end Ada.Units.Powers; + +with Ada.Long_Float_Text_IO; -- implementation-dependent choice +with Ada.Text_IO; +package body Ada.Units.System_Of_Units is + + package body Unitless is + function Val(Mag : Scale_Type) return Value is + begin + return Value(Mag); + end Val; + + function Mag(Val : Value) return Scale_Type is + begin + return Scale_Type(Val); + end Mag; + end Unitless; + + package body Private_Numeric is + -- Define multiplicative operators that combine with the unitless type. + -- Be careful not to recurse infinitely + -- No scaling is necessary + + use type Unitless.Value; + + function "*"(Left : Value; Right : Unitless.Value) + return Value is + begin + return Value(Unitless.Value(Left) * Right); + end "*"; + + function "*"(Left : Unitless.Value; Right : Value) + return Value is + begin + return Value(Left * Unitless.Value(Right)); + end "*"; + + function "/"(Left : Value; Right : Unitless.Value) + return Value is + begin + return Value(Unitless.Value(Left) / Right); + end "/"; + + function "/"(Left : Value; Right : Value) + return Unitless.Value is + begin + return Unitless.Value(Left) / Unitless.Value(Right); + end "/"; + + function Val(Mag : Scale_Type) return Value is + begin + return Value(Mag); + end Val; + + function Mag(Val : Value) return Scale_Type is + begin + return Scale_Type(Val); + end Mag; + end Private_Numeric; + + 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.Val(Unit_A.Scale_Factor / Unit_B.Scale_Factor * + Unit_A.Mag(From)); + end Scale; + + function Scale(From : Unit_B.Value) return Unit_A.Value is + begin + return Unit_A.Val(Unit_B.Scale_Factor / Unit_A.Scale_Factor * + Unit_B.Mag(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 Base_Unit.Val(Base_Unit.Mag(Left) + + Base_Unit.Mag(Scale(Right))); + end "+"; + + function "+"(Left : Scaled_Unit.Value; Right : Base_Unit.Value) + return Base_Unit.Value is + begin + return Right + Left; + end "+"; + + function "-"(Left : Base_Unit.Value; Right : Scaled_Unit.Value) + return Base_Unit.Value is + begin + return Base_Unit.Val( + Base_Unit.Mag(Left) - Base_Unit.Mag(Scale(Right))); + end "-"; + function "-"(Left : Scaled_Unit.Value; Right : Base_Unit.Value) + return Base_Unit.Value is + begin + return Base_Unit.Val( + Base_Unit.Mag(Scale(Left)) - Base_Unit.Mag(Right)); + end "-"; + function "-"(Right : Scaled_Unit.Value) + return Base_Unit.Value is + begin + return Base_Unit.Val(- Base_Unit.Mag(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.Val(Combined_Scale_Factor * + Unit_A.Mag(Left) * Unit_B.Mag(Right)); + end "*"; + function "*"(Left : Unit_B.Value; Right : Unit_A.Value) + return Unit_C.Value is + begin + return Unit_C.Val(Combined_Scale_Factor * + Unit_B.Mag(Left) * Unit_A.Mag(Right)); + end "*"; + function "/"(Left : Unit_C.Value; Right : Unit_A.Value) + return Unit_B.Value is + begin + return Unit_B.Val(Inverse_Combined_Scale_Factor * + Unit_C.Mag(Left) / Unit_A.Mag(Right)); + end "/"; + function "/"(Left : Unit_C.Value; Right : Unit_B.Value) + return Unit_A.Value is + begin + return Unit_A.Val(Inverse_Combined_Scale_Factor * + Unit_C.Mag(Left) / Unit_B.Mag(Right)); + end "/"; + + end Multiplicative_Operators; + + package body Squared_Unit is + + function "*"(Left : Base_Unit.Value; Right : Base_Unit.Value) + return Squared_Unit.Value is + begin + return Squared_Unit.Val( + Base_Unit.Mag(Left) * Base_Unit.Mag(Right)); + end "*"; + + function "/"(Left : Squared_Unit.Value; Right : Base_Unit.Value) + return Base_Unit.Value is + begin + return Base_Unit.Val( + Squared_Unit.Mag(Left) / Base_Unit.Mag(Right)); + end "/"; + + -- Declare squaring and square root operations + function Square(Val : Base_Unit.Value) return Squared_Unit.Value is + begin + return Val * Val; + end Square; + + function Sqrt(Val : Squared_Unit.Value) return Base_Unit.Value is + begin + return Base_Unit.Val(Powers.Sqrt(Squared_Unit.Mag(Val))); + end Sqrt; + + end Squared_Unit; + + package body Cubed_Unit is + -- Declare cubing and cube root operations + function Cube(Val : Base_Unit.Value) return Cubed_Unit.Value is + begin + return Cubed_Unit.Val(Base_Unit.Mag(Val) ** 3); + end Cube; + + function Cube_Root(Val : Cubed_Unit.Value) return Base_Unit.Value is + use Powers; -- for "**" on Scale_Type + begin + -- Raise to 1/3 power. + return Base_Unit.Val(Cubed_Unit.Mag(Val) ** (1.0/3.0)); + end Cube_Root; + + end Cubed_Unit; + + package body Square_Root_Unit is + -- Declare multiplicative operators + function "*"(Left : Square_Root_Unit.Value; + Right : Square_Root_Unit.Value) return Base_Unit.Value is + begin + return Base_Unit.Val( + Square_Root_Unit.Mag(Left) * Square_Root_Unit.Mag(Right)); + end "*"; + + function "/"(Left : Base_Unit.Value; Right : Square_Root_Unit.Value) + return Square_Root_Unit.Value is + begin + return Square_Root_Unit.Val( + Base_Unit.Mag(Left) / Square_Root_Unit.Mag(Right)); + end "/"; + + -- Declare squaring and square root operations + function Square(Val : Square_Root_Unit.Value) return Base_Unit.Value is + begin + return Val * Val; + end Square; + + function Sqrt(Val : Base_Unit.Value) return Square_Root_Unit.Value is + begin + return Square_Root_Unit.Val(Powers.Sqrt(Base_Unit.Mag(Val))); + end Sqrt; + end Square_Root_Unit; + + package body Arbitrary_Power_Unit is + -- Declare appropriate exponentiation operations + function Exponentiate_By_Power(Val : Base_Unit.Value) + return Arbitrary_Power_Unit.Value is + begin + return Arbitrary_Power_Unit.Val( + Base_Unit.Mag(Val) ** Power); + end Exponentiate_By_Power; + + function Exponentiate_By_Inverse_Power( + Val : Arbitrary_Power_Unit.Value) return Base_Unit.Value is + begin + return Base_Unit.Val( + Arbitrary_Power_Unit.Mag(Val) ** (1.0/Power)); + end Exponentiate_By_Inverse_Power; + + end Arbitrary_Power_Unit; + + -- Some compilers 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 Unit_For_Output.Value; + Fore : in Ada.Text_IO.Field := Default_Fore; + Aft : in Ada.Text_IO.Field := Default_Aft; + Exp : in Ada.Text_IO.Field := Default_Exp) is + IO_Val : constant IO_Type := IO_Type(Unit_For_Output.Mag(Val)); + begin + Float_IO.Put(IO_Val, + Fore => Fore, Aft => Aft, Exp => Exp); + Ada.Text_IO.Put(' ' & Name(Is_One => IO_Val = 1.0)); + end Put; + + procedure Put(File : in Ada.Text_IO.File_Type; + Val : in Unit_For_Output.Value; + Fore : in Ada.Text_IO.Field := Default_Fore; + Aft : in Ada.Text_IO.Field := Default_Aft; + Exp : in Ada.Text_IO.Field := Default_Exp) is + IO_Val : constant IO_Type := IO_Type(Unit_For_Output.Mag(Val)); + begin + Float_IO.Put(File, IO_Val, + Fore => Fore, Aft => Aft, Exp => Exp); + Ada.Text_IO.Put(File, ' ' & Name(Is_One => IO_Val = 1.0)); + end Put; + + function Image(Val : Unit_For_Output.Value; + Fore : Ada.Text_IO.Field := Default_Fore; + Aft : Ada.Text_IO.Field := Default_Aft; + Exp : Ada.Text_IO.Field := Default_Exp) return String is + + IO_Val : constant IO_Type := IO_Type(Unit_For_Output.Mag(Val)); + 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_Val, + Aft => Aft, Exp => Exp); + for I in Result'Range loop + if Result(I) /= ' ' then + return Result(I..Result'Last) & ' ' & + Name(Is_One => IO_Val = 1.0); + end if; + end loop; + return "??"; -- Should never happen + end Image; + + end Text_Output; + +end Ada.Units.System_Of_Units; + +**************************************************************** + +From: Pascal Leroy +Sent: Wednesday, February 5, 2003 7:44 AM + +> - The scaling prefixes used now conform to +> the SI standard, except for places where they require +> characters outside of the ISO 646 7-bit set (e.g. "u" +> rather than "<greek mu>"). The prefixes can be +> overridden via the Localization mechanism (see below). + +I know it's nitpicking, but I am going to object to any AI referencing ISO 646, +an obsolete, English-centric character set. ISO 8859-1 is a perfectly good +character set, which is supported by all computer systems that I know of, and +it has all the characters you need to properly deal with the SI system of units. + +Is there a smiley for French bad temper? + +**************************************************************** + +From: Tucker Taft +Sent: Wednesday, February 5, 2003 11:06 AM + +That's fine with me. I don't really know whether +all my local editing tools and such will deal reliably +with the full ISO 8859-1 set. I am still using "vi" +remember... ;-) But I presume someone on the ARG +can make sure mu and omega end up looking correct, +if we go with them. + +**************************************************************** + +From: Christoph Grein +Sent: Wednesday, February 5, 2003 11:12 PM + +Chill out, Pascal. + +> I know it's nitpicking, but I am going to object to any AI referencing ISO 646, +> an obsolete, English-centric character set. ISO 8859-1 is a perfectly good +> character set, which is supported by all computer systems that I know of, and it +> has all the characters you need to properly deal with the SI system of units. + +Latin_1.Micro_Sign = 'æ' (Character'Val (181), but there is no capital Greek +Omega needed for Ohm. + +**************************************************************** + +From: Pascal Leroy +Sent: Thursday, February 6, 2003 2:31 AM + +That's a good point, so you'd have to stick to "ohm" when using type String. + +But then this leads me to suggest that it would be good to have a +Wide_String variant (or, probably more appropriately, to pass the string +type as a generic parameter in various places). + +**************************************************************** + +From: Christoph Grein +Sent: Thursday, February 6, 2003 2:36 AM + +> That's a good point, so you'd have to stick to "ohm" when using type String. +> +> But then this leads me to suggest that it would be good to have a +> Wide_String variant (or, probably more appropriately, to pass the string + +perhaps + +> type as a generic parameter in various places). + +No, I think it's already complicated enough. Having 'æ' and 'u' for micro and +"Ohm" or "ohm" (I'd prefer the capitalised form "Ohm") instead of capital Omega +is OK for me. + +So leave it as simple as possible (but not simpler). + +**************************************************************** + +From: Jeffery Carter +Sent: Wednesday, February 5, 2003 12:01 PM + +I never saw the message that started this thread. Is this a problem on +my end, or have others had similar results, indicating something with +the list server? + +I haven't seen any responses to my recent post with revised data +structure packages. This may be because (in increasing order of +probability): + +* No one received the message +* I didn't receive the responses +* No one felt it worth commenting on +Tucker has clearly done a lot of work on this. I would like to thank him +for this, but can't resist mentioning a couple of nits. + +Tucker Taft wrote: +> generic +> Name : in String; +> Exponents : in Exponent_Array; +> Scale_Factor : in Scale_Type := 1.0; +> type Value is private; + +Put Scale_Factor here so positional notation can be used and still +accept all the defaulted parameters with their defaults. + +> with function Val(Mag : Scale_Type) return Value is <>; +> with function Mag(Val : Value) return Scale_Type is <>; +> package Unit_Signature is end Unit_Signature; + +This looks odd to me. I suppose I'll get used to it if it's used in the +standard. I'd prefer + +package Unit_Signature is + -- null; +end Unit_Signature; + +This clearly shows me that the package spec is deliberately empty. + +Off_Topic : begin + +As a general comment about language design (which I am very well +unqualified to make) I would have preferred it if the concept applied to +sequences of statements, that if they are empty they must contain a null +statement to show that they are deliberately empty, were extended to +other parts of the language that can contain statements but may be +empty. A declarative region, for example, would have to contain a null +statement if it contains no declarations: + +generic -- Sig package + -- Whatever +package Sig is + null; +end Sig; + +Obviously this has no chance of being incorporated in Ada. + +Thank you for putting up with my digression. + +end Off_Topic; + +**************************************************************** + +From: Christoph Grein +Sent: Friday, February 7, 2003 4:52 AM + +Just a little missprint: + +Symbol for pressure unit Pascal is Pa, see Ada.Units.SI_Units.Derived_Units. + +**************************************************************** + +From: Christoph Grein +Sent: Monday, February 10, 2003 5:22 AM + +I'm still not convinced but I like this more than the previous proposal. + +1. Wrong dimension + +The proposal leads to incorrect names for units is some cases. + + 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). + +That's plain wrong. Of course radian = m/m = 1, so it has no dimension. +But this unit is used to say that the value is an angle, and an angle +squared does not make sense nor an angle divided by another angle. + +If you have sin (x^2), then x^2 is of dimension radian, not x. + +Show me an equation where you find an angle squared. You won't find one. + +This is the same mistake as denoting a frequency with Becquerel. +Hz and Bq are both 1/s, but they may not be used arbitrarily. + +A unit multiplied by a number may require a different unit, although the +dimension remains the same: Angular frequencies are not measured in Hz. + + f = 50 Hz, then omega = 2 pi f = 314/s -- Hz is wrong for omega + +Torque is measured in Newton*Meter, not Joule. Rotational work equation +is similar to W = F * d => W = T * phi + + [W] = J, [F] = N, [d] = m, [T] = N * m, [phi] = rad + +The proposal forces one to use Joule instead of Newtonmeter. If you define +Newtonmeter as a separate unit, you'll get unresolvable ambiguities. + +2. The definition of power units is really clever. My only point is that +Exponentiate_By_Power does not tell you anything: + + S = sigma * Exponentiate_By_Power (T) ?? + +You need an extra renaming after instantiation to make this make sense, e.g.: + + S = sigma * Power_4 (T) + +3. This leads me to a very important point. I am persistent with disliking +the combinatorial explosion. + +You have just defined the SI units. But that's not all you have to do - the +work proper has only just begun. + +Who is going to define the operators for mixing all these units. You'll need +hundreds of instantiations of Multiplicative_Operators. Who is going to do +this? Will the poor user of this proposal have to do them? And will he or she +then have in every package using SI to add a context clause with hundreds of +packages? + +Or do you intend to include them all in e.g. Ada.Units.Full_SI_Operators so +that there will be just one unit in a with and a use clause? + +4. Polynomials cannot be defined. + + type Table is array (Natural range <>) of Item; + + function Polynomial (Coefficients: Table; X: Item) return Item is + -- Use Horner's scheme. + F: Item := (Value => 0.0, + Unit => Coefficients (Coefficients'Last).Unit / X.Unit); + begin + for I in reverse Coefficients'Range loop + F := F * X + Coefficients (I); + end loop; + return F * X ** Coefficients'First; + end Polynomial; + +5. As much as I like the Name_Support for Product_Name, Quotient_Name, +Power_Name, I dislike the Localization with plurals. Units in SI do not +have plurals, show me the place at NIST where there are plurals. + +Plurals are for natural language, and natural language support should be +off limits for Ada standard. + +You may pronounce 5 Kilometers, but you write 5 km. + +6. There is no Text Input. This would also be very difficult with this +proposal since there is no character in between the value and the unit +on output: 5 km. + +How can you read this in? How do you discriminate between reading a +dimensionless value or a value with a unit? You need a character (other +than blank) in between. + +I do think, proper Text_IO is far more important than Localization. + +7. There is still no maths. + + Sin (5 dm, cycle => 1 m) + +This is also more important than Localization. +-------------------------------------------------------------------------- +I repeat my statement: + + 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. + +Just make an experiment and you'll experience combinatorial explosion: + +Set up all operators you need to handle such simple equations as the +following (of course solved for any item involved and complete freedom +of the sequence of factors): + +Pressure p = rho g h + +Buoyancy F = rho g V = p A + +Kepler T^2 / a^3 = const + +Newton F = G m1 m2 / r^2 + +Centrif. F = v^2 / r = r omega^2 + +There is no package for Kilogramm per Cubicmeter (Density rho)... + +I'm sure you will need a lot more operators than you would think (only three +dimensions involved). Let me make a guess: You'll need about thirty +instantiations. + +If you're going to define all combinations that might be needed for all +7 units, fine. I'm waiting. You can't let this do the user of Ada.Units. +I wouldn't do this, rather not use it. + +Perhaps this proposal could be combined with Macks, a code generator +by Fraser Wilson for physical units I'm going to present in Toulouse. + +**************************************************************** + +From: Tucker Taft +Sent: Monday, February 10, 2003 3:21 PM + +Thank you again for your comments. I am not a practicing +physicist or chemist, so I have only distant memories +of using these units from my college days. Please have +patience with my mistakes ;-). + +I have some further responses below... + +>... +> If you have sin (x^2), then x^2 is of dimension radian, not x. +> +> Show me an equation where you find an angle squared. You won't find one. + +I guess I considered the Taylor expansion of Sin and Cos +examples of equations that have various powers of angles, and +proceed to add them together. + +But I can also see that in most other contexts, allowing +radians * radians => radians doesn't make sense. I will +pass these comments on to Thom Brooke, as he was the one +who suggested the idea of Dimensionless units. Perhaps +he had something else in mind. + +... +> The proposal forces one to use Joule instead of Newtonmeter. If you define +> Newtonmeter as a separate unit, you'll get unresolvable ambiguities. + +I think you will get the ambiguities only if you have +simultaneous "use" clauses in effect for Joules and Newton_Meters. + + +> 2. The definition of power units is really clever. My only point is that +> Exponentiate_By_Power does not tell you anything: +> +> S = sigma * Exponentiate_By_Power (T) ?? +> +> You need an extra renaming after instantiation to make this make sense, e.g.: +> +> S = sigma * Power_4 (T) + +The intent was that you would not "use" these packages, +only do a "use type" on the pkg.Value type. +The operations like Exponentiate_By_Power would always +be prefixed by the name of the unit, so hopefully +that would give a hint as to the power. For example, +it isn't quite so mysterious if you see + + Meters_To_The_Fourth.Exponentiation_By_Power(metr) + +But you are right, it is a bit mysterious, and the documentation +should suggest using a rename rather than a "use" clause. + +> 3. This leads me to a very important point. I am persistent with disliking +> the combinatorial explosion. +> +> You have just defined the SI units. But that's not all you have to do - the +> work proper has only just begun. +> +> Who is going to define the operators for mixing all these units. You'll need +> hundreds of instantiations of Multiplicative_Operators. Who is going to do +> this? Will the poor user of this proposal have to do them? And will he or she +> then have in every package using SI to add a context clause with hundreds of +> packages? + +My recommendation is that users would instantiate the +Multiplicative_Operators package themselves, and only as +necessary for their particular usage pattern. Lettings users +do their "own" instantiations is safe, because the units are checked +on each instantiation using the "Assert." + +I agree that it is impractical to provide all the operations +that anyone could ever need. But my expectation is that +in any given project, or certainly in any given package body, +a relatively small number of instantiations would be +necessary. + +> Or do you intend to include them all in e.g. Ada.Units.Full_SI_Operators so +> that there will be just one unit in a with and a use clause? + +No, I don't think that is practical. + +... +> 5. As much as I like the Name_Support for Product_Name, Quotient_Name, +> Power_Name, I dislike the Localization with plurals. Units in SI do not +> have plurals, show me the place at NIST where there are plurals. + +An important feature of the System_Of_Units generics is that +it can be used for "unit-like" purposes that have nothing +to do with chemistry or physics or SI. I agree that the +preinstantiation for SI should avoid plurals, as they aren't +used there, and I believe because all are <= 3 in length, +no plurals will be generated. However, I can also imagine +using System_Of_Units for other sorts of compile-time +checking, such as the distinction between bit counts, +byte counts, and word counts versus "pure" numbers. +In situations like these, plurals might be of more use. + +> Plurals are for natural language, and natural language support should be +> off limits for Ada standard. +> +> You may pronounce 5 Kilometers, but you write 5 km. + +I agree that for SI purposes, plurals are inappropriate. +But plenty of Ada output is intended to be more +"conversational" than that appropriate for scientific +calculations, and for these purposes, having control +over pluralization might be of some use. It is also +easy to turn it off completely by setting the maximum-no-plurals +at Integer'Last. + +> 6. There is no Text Input. This would also be very difficult with this +> proposal since there is no character in between the value and the unit +> on output: 5 km. +> +> How can you read this in? How do you discriminate between reading a +> dimensionless value or a value with a unit? You need a character (other +> than blank) in between. + +I wasn't sure what were the requirements for text input. +If you can specify them, I am happy to turn the requirements into code. + +> I do think, proper Text_IO is far more important than Localization. +> +> 7. There is still no maths. +> +> Sin (5 dm, cycle => 1 m) +> +> This is also more important than Localization. + +Again, having requirements for what would be useful +in this area would help. My expertise is mostly +in using the features of the language to get more +compile-time, or instantiation-time, checking. + +... +> If you're going to define all combinations that might be needed for all +> 7 units, fine. I'm waiting. You can't let this do the user of Ada.Units. +> I wouldn't do this, rather not use it. + +I'm not sure I understand this comment, but I was +expecting users to do some number of instantiations +themselves. The System_Of_Units generic and its +nested generics were designed to allow the user to +do safe instantiations relatively easily to match +the needs of a particular project or a particular +piece of code. Since the instantiations themselves generate +little or no code, having several of them spread around +should not incur measurable overhead. The SI_Units +preinstantiations would hopefully either be directly +useful, or useful as a model. + +> Perhaps this proposal could be combined with Macks, a code generator +> by Fraser Wilson for physical units I'm going to present in Toulouse. + +That would be interesting to investigate. + +**************************************************************** + +From: Christoph Grein +Sent: Tuesday, February 11, 2003 12:32 AM + +> Thank you again for your comments. I am not a practicing +> physicist or chemist, so I have only distant memories +> of using these units from my college days. Please have +> patience with my mistakes ;-). + +No problem - it's just that what I'm after, try to practice physics with this +proposal and see how it works out. I'm full of doubt. Ask physicists in the Ada +community to use it on their problems at hand. + +> I guess I considered the Taylor expansion of Sin and Cos +> examples of equations that have various powers of angles, and +> proceed to add them together. + +For small x, sin x = x. But as an argument of sin, x may have the unit rad, +whereas the result is a pure number, no unit rad! + +You see, dimensionless numbers have their quirks if you put a pseudo-dimension +on them. + +Units in SI carry a connotation of what they measure, not only a dimension. + +> > Torque is measured in Newton*Meter, not Joule. Rotational work equation +> > is similar to W = F * d => W = T * phi +> > +> > [W] = J, [F] = N, [d] = m, [T] = N * m, [phi] = rad +> > +> > The proposal forces one to use Joule instead of Newtonmeter. If you define +> > Newtonmeter as a separate unit, you'll get unresolvable ambiguities. +> +> I think you will get the ambiguities only if you have +> simultaneous "use" clauses in effect for Joules and Newton_Meters. + +But I would need them exactly then when I have such equations with torque and +work. + +And do you really think it is practicable to select among the dozens or more use +clauses depending on the kind of equations you are going to handle? + +> > 2. The definition of power units is really clever. My only point is that +> > Exponentiate_By_Power does not tell you anything: +> > +> > S = sigma * Exponentiate_By_Power (T) ?? +> > +> > You need an extra renaming after instantiation to make this make sense, +e.g.: +> > +> > S = sigma * Power_4 (T) +> +> The intent was that you would not "use" these packages, +> only do a "use type" on the pkg.Value type. +> The operations like Exponentiate_By_Power would always +> be prefixed by the name of the unit, so hopefully +> that would give a hint as to the power. For example, +> it isn't quite so mysterious if you see +> +> Meters_To_The_Fourth.Exponentiation_By_Power(metr) + +Oh no, please, that's a joke ? :-( That's just too much noise. + +> But you are right, it is a bit mysterious, and the documentation +> should suggest using a rename rather than a "use" clause. +> > +> > 3. This leads me to a very important point. I am persistent with disliking +> > the combinatorial explosion. +> > +> > You have just defined the SI units. But that's not all you have to do - the +> > work proper has only just begun. +> > +> > Who is going to define the operators for mixing all these units. You'll need +> > hundreds of instantiations of Multiplicative_Operators. Who is going to do +> > this? Will the poor user of this proposal have to do them? And will he or +she +> > then have in every package using SI to add a context clause with hundreds of +> > packages? +> +> My recommendation is that users would instantiate the +> Multiplicative_Operators package themselves, and only as +> necessary for their particular usage pattern. Lettings users +> do their "own" instantiations is safe, because the units are checked +> on each instantiation using the "Assert." + +The last sentence may be true, but it is still impractical. This is my strongest +objection against this method. + +You want to do physics and not fiddling with use clauses every now and then. + +Oh, @!?, this does not compile - ah, I'm missing a use clause, but which one? +... Hm, better I rearrange the factors - @!?@!? now it again does not compile, +missing another use clause - oh no, @!?@!?!?@!, now it's ambiguous and I need to +qualify, or can I remove one of my dozen or so use clauses now? + +Until there is a consolidated set of use clauses for a given problem domain, +this method will more impede than help you. + +> I agree that it is impractical to provide all the operations +> that anyone could ever need. But my expectation is that +> in any given project, or certainly in any given package body, +> a relatively small number of instantiations would be +> necessary. + +What is small? Is 50 small? See the simple mechanical equations below. + +> > 6. There is no Text Input. This would also be very difficult with this +> > proposal since there is no character in between the value and the unit +> > on output: 5 km. +> > +> > How can you read this in? How do you discriminate between reading a +> > dimensionless value or a value with a unit? You need a character (other +> > than blank) in between. +> +> I wasn't sure what were the requirements for text input. +> If you can specify them, I am happy to turn the +> requirements into code. + +The requirements are simple, I think. What you print out, you should be able to +read in again, with full unit checking. + +So reading a line like + + 5.0 20.4 km/s 1.0e+6 ms 100.0 50.0 N + +should be possible. But this is quite impossible. The following is better: + + 5.0 20.4*km/s 1.0e+6*ms 100.0 50.0*N + +You read a number, a speed, a time, a number, a force. + +> > I do think, proper Text_IO is far more important than Localization. +> > +> > 7. There is still no maths. +> > +> > Sin (5 dm, cycle => 1 m) +> > +> > This is also more important than Localization. +> +> Again, having requirements for what would be useful +> in this area would help. My expertise is mostly +> in using the features of the language to get more +> compile-time, or instantiation-time, checking. + +All math functions with dimensioned arguments (dim /= 1) are nonsense with the +exception of rational powers and trigonometric functions and their inverses with +a cycle parameter. + +... +> > If you're going to define all combinations that might be needed for all +> > 7 units, fine. I'm waiting. You can't let this do the user of Ada.Units. +> > I wouldn't do this, rather not use it. +> +> I'm not sure I understand this comment, but I was +> expecting users to do some number of instantiations +> themselves. + +See above. A method should not introduce noise into the equations, or they will +induce errors without affecting the dimensions. If you have to write something +like + + 4.5 * Meters_To_The_Fourth.Exponentiation_By_Power(metr) * + Another_long.expanded_name (PI * X) + +you very easily overlook a dimensionless factor in all that noise. This is then +worse than calculating with no dimension checking at all: + + 4.5 * Metr**4 * f (Pi * X)function Trig (x, cycle: Item) return +Dimensionless; + +x and cycle must have the same dimension. + +**************************************************************** + +From: Christoph Grein +Sent: Tuesday, February 11, 2003 4:10 AM + +BTW: These SI prefixes (Kilo, Mega...) are, AFAIK, only allowed on SI units +(don't know whether they are allowed on øC) and some others for historical +reasons (e.g. eV, l, pc), but e.g. not on h, min. + +What is their use with miles, yards etc? Have there ever been kiloyards or +milliinches? + +**************************************************************** + +From: John Barnes +Sent: Tuesday, February 11, 2003 10:16 AM + +Certainly not so far as I know. Nor with typesetting units +such as picas and points which are duodecimal. + +**************************************************************** + +From: Tucker Taft +Sent: Tuesday, February 11, 2003 1:04 PM + +According to the NIST website (http://physics.nist.gov/cuu/Units/) +prefixes are generally permitted on other units outside the "core" +set, and on degrees Centigrade. However, prefixes are explicitly +discouraged on a few of the units, in particular h, min, ', '', +and degrees latitude/longitude. + +> > What is their use with miles, yards etc? Have there ever been kiloyards or +> > milliinches? +> +> Certainly not so far as I know. Nor with typesetting units +> such as picas and points which are duodecimal. + +They are used on other kinds of things, though. I mentioned +bits and bytes. Dollars and cents are another example, where +kilo, mega, and giga are sometimes used (e.g. kbits, M$, Gbytes). + +Bits and bytes bring up a separate discussion, where the IEC has +standardized prefixes which represent 2**10, 2**20, etc. as opposed to +the approximately equal decimal values 1000, 10**6, etc. I have +never seen them in use ("ki" for 1024, "Gi" for 2**30, etc.). +In any case, the SI prefixes seem to be spreading into other +areas of use, probably as their use with personal computer +performance characteristics has spread. + +**************************************************************** + +From: Christoph Grein +Sent: Wednesday, February 12, 2003 5:04 AM + +Nor have I, but I'm beginning to use them, and we all should do, because it +does make a difference: + + Does the milbus transfer 1Mbit/s or 1 Mibit/sec? + +Pronunciation: Megabit vs. Mebibit, Kilobit vs. Kibibit ... the bi stands for +binary. + +**************************************************************** + +From: Adam Beneschan +Sent: Tuesday, February 11, 2003 10:25 AM + +> > I guess I considered the Taylor expansion of Sin and Cos +> > examples of equations that have various powers of angles, and +> > proceed to add them together. +> +> For small x, sin x = x. But as an argument of sin, x may have the +> unit rad, whereas the result is a pure number, no unit rad! +> +> You see, dimensionless numbers have their quirks if you put a +> pseudo-dimension on them. + +Radians are just special. All the other units I can think of are +arbitrary; someone had to decide how big a meter or a gram or a second +would be, and there is no particular reason why choosing those +particular sizes for those units would be any better or worse than +choosing some other arbitrary size. The size of a radian, however, is +not arbitrary---it's the angle such that the length of the arc of a +circle subtended by the angle is equal to the radius of the circle. +This makes it special mathematically; it's the reason why a value +measured in radians can be used in a Taylor expansion, while I don't +believe it would make any sense to use a value measured in meters or +joules or troy ounces or anything else in this manner. + +**************************************************************** + +From: Christoph Grein +Sent: Wednesday, February 12, 2003 4:44 AM + +Hi, I did not want to look too pessimistic, so I played a bit with + + p = rho g h . + +So even with this litte example, I needed 10 further instantiations and already +lost a bit the orientation as to which units I can combine and which not because +of missing instantiations. Names became long and longer. + +It was a tedious job, and on the way I was a bit confused because I had all +instantiations, yet it wouldn't compile, until I realised I missed a use clause. + +So I'm still pessimistic. Do you really think you can leave all this to the poor +programmer? + +Code below. + +As to the problem with Joule and Newtonmeter. Perhaps a renming of package +Joule to Newton_Meter rather than a separate package would avoid the problems. +There is then the Name parameter problem. + +Units do not only denote a dimension, they also show what is measured, so there +are different names for the same dimension. And rad has been introduced to solve +the problem that e.g. 5 might be misleading, but 5 rad tells you that this +denotes an angle, whereas alpha = 1/137 really is just a number. + +So 5/s does not tell you whether it's a frequency or a circular frequency or a +decay rate, but 5 Hz definitely denotes a frequency, 5 rad/s denotes a circular +frequency, 5 Bq denotes a decay rate. + +So perhaps it would be best to have only one package One_per_Second and handle +the different aspects by renamings. The Name parameter problem remains to be +solved - we need to be able to give alternative names. +-------------------------------------------------------------------- +with Ada.Units.SI_Units.Derived_Units; +use Ada.Units.SI_Units.Derived_Units, Ada.Units.SI_Units; + +package Mechanics is + + package Meter_Squared_per_Second_Squared is + new SI.Squared_Unit (Meter_Per_Second.Sig); + + package Kilogramm_per_Square_Meter is + new SI.Quotient_Unit (Kilogram.Sig, Square_Meter.Sig); + package Kilogramm_per_Cubic_Meter is + new SI.Quotient_Unit (Kilogram.Sig, Cubic_Meter.Sig); + + package Pascal_per_Meter is + new SI.Quotient_Unit (Pascal.Sig, Meter.Sig); + + package Kilogramm_per_Square_Meter_times_Meter_Per_Second_Squared is + new SI.Multiplicative_Operators (Kilogramm_per_Square_Meter.Sig, + Meter_Per_Second_Squared.Sig, + Pascal.Sig); + package Kilogramm_per_Cubic_Meter_times_Meter is + new SI.Multiplicative_Operators (Kilogramm_per_Cubic_Meter.Sig, + Meter.Sig, + Kilogramm_per_Square_Meter.Sig); + package Kilogramm_per_Cubic_Meter_times_Meter_Per_Second_Squared is + new SI.Multiplicative_Operators (Kilogramm_per_Cubic_Meter.Sig, + Meter_Per_Second_Squared.Sig, + Pascal_per_Meter.Sig); + package Pascal_per_Meter_times_Meter is + new SI.Multiplicative_Operators (Pascal_per_Meter.Sig, + Meter.Sig, + Pascal.Sig); + package Meter_per_Second_Squared_times_Meter is + new SI.Multiplicative_Operators (Meter_per_Second_Squared.Sig, + Meter.Sig, + Meter_Squared_per_Second_Squared.Sig); + package Meter_Squared_per_Second_Squared_times_Kilogramm_per_Cubic_Meter is + new SI.Multiplicative_Operators (Meter_Squared_per_Second_Squared.Sig, + Kilogramm_per_Cubic_Meter.Sig, + Pascal.Sig); + +end Mechanics; +with Ada.Units.SI_Units.Derived_Units; +use Ada.Units.SI_Units.Derived_Units, Ada.Units.SI_Units; +with Mechanics; +use Mechanics; + +procedure Do_Mechanics is + + use Kilogramm_per_Cubic_Meter_times_Meter, + Kilogramm_per_Cubic_Meter_times_Meter_Per_Second_Squared, + Kilogramm_per_Square_Meter_times_Meter_Per_Second_Squared, + Pascal_per_Meter_times_Meter, + Meter_per_Second_Squared_times_Meter, + Meter_Squared_per_Second_Squared_times_Kilogramm_per_Cubic_Meter; + + Gravity: constant Meter_Per_Second_Squared.Value + := Meter_Per_Second_Squared.Val (8.81); + + Rho : Kilogramm_per_Cubic_Meter.Value := + Kilogramm_per_Cubic_Meter.Val (1000.0); + Height: Meter.Value := Meter.Val (10.0); + + Pressure: Pascal.Value; + +begin + + Pressure := Rho * Gravity * Height; + Pressure := Gravity * Rho * Height; + Pressure := Height * Rho * Gravity; + Pressure := Rho * Height * Gravity; + Pressure := Gravity * Height * Rho; + Pressure := Height * Gravity * Rho; + +end Do_Mechanics; + +**************************************************************** + +From: Tucker Taft +Sent: Wednesday, February 12, 2003 2:03 PM + +... +> So I'm still pessimistic. Do you really think you can leave all this to the +> poor programmer? + +Well, I guess I wouldn't expect any "real" program to use all of these +combinations, at least not all in such close proximity. + +But I may be wrong. Do you have some examples of "real" code where +one might test this hypothesis? + +I might also suggest a paradigm where the instantiations of the +SI.Multiplicative_Operators generic are done local to the point of use, +followed immediately by the "use" clause. E.g.: + + package Pascal_Ops1 is new SI.Multiplicative_Operators( + Kilogramm_Per_Square_Meter.Sig, Meter_Per_Second_Squared.Sig, Pascal.Sig); + use Pascal_Ops1; + +This means that the name of the instantiation is less important, +and what you are effectively doing is a parameterized "use" clause, +in effect "use SI.Multiplicative_Operators(Unit1.sig, Unit2.sig, Unit3.sig);". + +> Code below. +> +> As to the problem with Joule and Newtonmeter. Perhaps a renming of package +> Joule to Newton_Meter rather than a separate package would avoid the problems. +> There is then the Name parameter problem. + +The "Name" can be supplied on instantiation of the Text_Output package +as well (actually "Singular" and "Plural" are separate parameters), +so even if you had only one instantiation of the Unit package +for the two distinct units, you could have more than one instantiation of +the Text_Output package, each with an appropriate Singular/Plural specified. + +By the way, you have convinced me about dropping all the pluralization stuff. +The next version won't have anything remaining of that, so the Text_Output +generic will take just a "Name" parameter, defaulted to the name of the unit +passed in. + +> Units do not only denote a dimension, they also show what is measured, so there +> are different names for the same dimension. And rad has been introduced to solve +> the problem that e.g. 5 might be misleading, but 5 rad tells you that this +> denotes an angle, whereas alpha = 1/137 really is just a number. +> +> So 5/s does not tell you whether it's a frequency or a circular frequency or a +> decay rate, but 5 Hz definitely denotes a frequency, 5 rad/s denotes a circular +> frequency, 5 Bq denotes a decay rate. +> +> So perhaps it would be best to have only one package One_per_Second and handle +> the different aspects by renamings. The Name parameter problem remains to be +> solved - we need to be able to give alternative names. + +As mentioned above, the "name" is mostly relevant on input/output. +So long as you can specify a name string at each instantiation of the Text_Output +package, I would think that would be adequate. + +**************************************************************** + +From: Christoph Grein +Sent: Thursday, February 13, 2003 12:26 AM + +> Well, I guess I wouldn't expect any "real" program to use all of these +> combinations, at least not all in such close proximity. + +But that is exactly my point: It is a nuisance in every instance to check which +operators are needed and to write exactly the required with and use clauses. +What we need is a simple to use package, not one that urges us to fiddle with +context clauses. + +This is doing the work twice (or even thrice): The SRS writer has probably +checked the equation. Now I'm evaluating the operators to see which context +clauses I need, i.e. I'm checking the dimensions again - and then the compiler +checks them a third time. + +> But I may be wrong. Do you have some examples of "real" code where +> one might test this hypothesis? + +This is exactly my other point. Feed this to the lions around doing physics and +see how they digest it. After this experience, go to standardise it (this will +then be Ada1Z :-) + +I personally do not have such code to try out. Our project is avionics, and +there the physics is so simple that it isn't worth the effort (the complicated +parts are provided by other companies - equipment software). We have a +different method to handle units (this is the only method of the four I'm going +to present in Toulouse that has seen industrial use in several projects - at +least four I know of). + +> I might also suggest a paradigm where the instantiations of the +> SI.Multiplicative_Operators generic are done local to the point of use, +> followed immediately by the "use" clause. E.g.: +> +> package Pascal_Ops1 is new SI.Multiplicative_Operators( +> Kilogramm_Per_Square_Meter.Sig, Meter_Per_Second_Squared.Sig, Pascal.Sig); +> use Pascal_Ops1; + +This is even more annoying :-( When do I get to the real code? More and more am +I forced to lose my time with local instantiations. + +> > As to the problem with Joule and Newtonmeter. Perhaps a renming of package +> > Joule to Newton_Meter rather than a separate package would avoid the +> > problems. There is then the Name parameter problem. +> +> The "Name" can be supplied on instantiation of the Text_Output package +> as well (actually "Singular" and "Plural" are separate parameters), +> so even if you had only one instantiation of the Unit package +> for the two distinct units, you could have more than one instantiation of +> the Text_Output package, each with an appropriate Singular/Plural specified. + +Yes, I've noticed that, but I thought there could perhaps be a better solution. + +I think we are (slowly) approximating a useful set of packages :-) + +**************************************************************** + +From: Christoph Grein +Sent: Thursday, February 13, 2003 1:00 AM + +> As mentioned above, the "name" is mostly relevant on input/output. +> So long as you can specify a name string at each instantiation of the +> Text_Output package, I would think that would be adequate. + +I also think that's the way to go. Then we should define the "standard name" as +as generally as possible, i.e. not "Hz", but "1/s", and use the specific names +on the IO (note the "I") instantiations. + +**************************************************************** + +From: Tucker Taft +Sent: Thursday, February 13, 2003 9:36 AM + +Back to the subject of unit-ized Input. + +Is the desired approach that Get checks to see if the unit name +is there, and if so, that it matches what is expected? +Or should Get allow the use of an arbitrary +SI prefix, like "k" or "c" so long as the base unit name matched, +and Get would automatically scale the value as necessary? + +Also, should the unit name be optional or mandatory, +whether or not automatic scaling is provided? + +I suppose the question comes up on output as well. +Should automatic scaling be provided by Put to keep the value +within some desired range (e.g. 0.5 .. 1000.0), or +should Put always use the unit specified in the instantiation, +with no scaling? + +**************************************************************** + +From: Christoph Grein +Sent: Friday, February 14, 2003 1:19 AM + + generic + with package Unit_For_Output is new Unit_Signature (<>); + Name: in String := Unit_For_Output.Name; -- called Singular in current spec + package Text_IO is + + procedure Put (File: in Ada.Text_IO.File_Type; + Val : in Unit_For_Output.Value; + Fore: in Ada.Text_IO.Field := Default_Fore; + Aft : in Ada.Text_IO.Field := Default_Aft; + Exp : in Ada.Text_IO.Field := Default_Exp); + -- Ouputs Val like Float_IO (with given Fore, Aft, Exp format), then + -- adds the Name. + -- Examples + -- 300.50*kg 50.0/ms 1.0*rad 5.0 (dimensionless) + +There is a problem with this package: There is no check that Name is appropriate +for Unit_For_Output. If e.g. Unit_For_Output => Kilogramm and Name => "J", an +exception should be raised. + +For Unit_For_Output => Kilogramm and Name => "g", an automatic scaling should be +applied. + +As it stands, Name is just a string, and it's easy to fall into this trap. Ada +is generally free of such (albeit simple to detect) traps. + + procedure Get (File: in Ada.Text_IO.File_Type; + Val : out Unit_For_Output.Value); + -- Inputs Value like Float_IO, then, if a unit follows (next character + -- one of *, or /), reads it (syntax to be defined, a missing unit is + -- interpreted as 1), then checks whether it is commensurable with the + -- given unit's signature. If it is, automatic scaling is done, + -- else Data_Error is raised. + -- Examples + -- 1.0 a unitless number + -- 1.0*rad another unitless number + -- 1.0*g if read for kg, 1.0E-3 will be result + +A possible syntax for reading units could be: + + The dimension is read as a product of units. Reading stops if + a unit is not followed by a '*' or '/', in any case at the end of + the line. + Each unit factor is read as follows: + Characters are input only so long as the sequence input is an + initial sequence of a literal having the syntax of a unit factor + (in particular, input ceases when a line terminator is encounter- + ed). The character or line terminator that causes input to cease + remains available for subsequent input. [These two sentences mimic + the behaviour for enumeration literals in RM A.10.6(5).] + The exception Data_Error is propagated if the sequence input does + not have the required syntax, or if the characters input for the + unit symbol do not form an existing symbol. + +It would be nice if there were a way to read any dimensioned value (without +previous knowledge of the dimension that will be read) and return the proper +type. I do not see how this could be done with this proposal. + +**************************************************************** + +From: Christoph Grein +Sent: Friday, February 14, 2003 6:16 AM + +> Characters are input only so long as the sequence input is an +> initial sequence of a literal having the syntax of a unit factor + +To define the syntax for a unit factor name, we could perhaps add to +Name_Support.Localization a component holding the set of legal characters in a +name. For SI this would be like an Ada identifier except '_': 'a'..'z', +'A'..'Z'. + +For cosmology or astronomy, one could also allow the underline, so that a unit +name could e.g. be "Sun_Mass" and an output would look like 4.0E+6*Sun_Mass or +4.0*MSun_Mass, i.e. 4 Million Sun Masses for the mass of a big black hole. + +Each (simple) name would then have to consist of letters in this set or else an +exception Name_Support.Name_Error would be raised. + +**************************************************************** + +From: Tucker Taft +Sent: Friday, February 14, 2003 9:45 AM + +... +> procedure Put (File: in Ada.Text_IO.File_Type; +> Val : in Unit_For_Output.Value; +> Fore: in Ada.Text_IO.Field := Default_Fore; +> Aft : in Ada.Text_IO.Field := Default_Aft; +> Exp : in Ada.Text_IO.Field := Default_Exp); +> -- Ouputs Val like Float_IO (with given Fore, Aft, Exp format), then +> -- adds the Name. +> -- Examples +> -- 300.50*kg 50.0/ms 1.0*rad 5.0 (dimensionless) + +I am now using the "Middle_Dot" character by default, rather than "*", +for product units, so we might want to use that here as well. Alternatively, +why not just use one space? That seems to be what the SI style +guide recommends. It is no particular problem to look ahead +across a space to see if there is a unit name following. + +> +> There is a problem with this package: There is no check that Name is appropriate +> for Unit_For_Output. If e.g. Unit_For_Output => Kilogramm and Name => "J", an +> exception should be raised. +> +> For Unit_For_Output => Kilogramm and Name => "g", an automatic scaling should be +> applied. +> +> As it stands, Name is just a string, and it's easy to fall into this trap. Ada +> is generally free of such (albeit simple to detect) traps. + +I can't imagine quite how this check would work, presuming that there +might be non-SI uses for the package. I suppose we could build up a +table of all instantiations of _Unit generics for a given instantiation of System_Of_Units, +and make sure that if the Name string passed in corresponds to some instantiation of +a _Unit package, then that instantiation's exponent array matches that of the +instantiation passed as the formal package parameter. It's not clear +this sort of check is worth it. Probably simpler would be to take +a second instantiation rather than a Name string, and the Name associated with +that instantiation would be used for output, even though the type would be from +the first instantiation. + +Alternatively, we keep it simple, encourage people to allow the Name string +to default, and then call Scale (or unary "+") if you want to pass a value +of a different unit but with the same dimension exponents. + +> procedure Get (File: in Ada.Text_IO.File_Type; +> Val : out Unit_For_Output.Value); +> -- Inputs Value like Float_IO, then, if a unit follows (next character +> -- one of *, or /), reads it (syntax to be defined, a missing unit is +> -- interpreted as 1), then checks whether it is commensurable with the +> -- given unit's signature. If it is, automatic scaling is done, +> -- else Data_Error is raised. +> -- Examples +> -- 1.0 a unitless number +> -- 1.0*rad another unitless number +> -- 1.0*g if read for kg, 1.0E-3 will be result + +Again, I would think we just allow/require a trailing unit name with one +space separating it from the number, rather than using "*" or middle dot. + +> A possible syntax for reading units could be: +> +> The dimension is read as a product of units. Reading stops if +> a unit is not followed by a '*' or '/', in any case at the end of +> the line. +> Each unit factor is read as follows: +> Characters are input only so long as the sequence input is an +> initial sequence of a literal having the syntax of a unit factor +> (in particular, input ceases when a line terminator is encounter- +> ed). The character or line terminator that causes input to cease +> remains available for subsequent input. [These two sentences mimic +> the behaviour for enumeration literals in RM A.10.6(5).] +> The exception Data_Error is propagated if the sequence input does +> not have the required syntax, or if the characters input for the +> unit symbol do not form an existing symbol. +> +> It would be nice if there were a way to read any dimensioned value (without +> previous knowledge of the dimension that will be read) and return the proper +> type. I do not see how this could be done with this proposal. + +I can't imagine that you would be in a position where you would want +a number of unknown dimension. I could see not caring whether you +get kg or g, but that seems to be a scaling thing. + +Perhaps we should record with all instantiations of _Unit packages what +is the "base" unit name separately from the scaling prefix, and allow +any unit name that has the same base unit name, but potentially a different +scaling prefix, and scaling would be performed automatically. + +In any case, as far as my original question, are you saying that the +unit name should be required, and automatic scaling should be performed? +The only possible exception might be for dimensionless values. + +**************************************************************** + +From: Christoph Grein +Sent: Tuesday, February 18, 2003 3:43 AM + +> > procedure Put (File: in Ada.Text_IO.File_Type; +> > Val : in Unit_For_Output.Value; +> > Fore: in Ada.Text_IO.Field := Default_Fore; +> > Aft : in Ada.Text_IO.Field := Default_Aft; +> > Exp : in Ada.Text_IO.Field := Default_Exp); +> > -- Ouputs Val like Float_IO (with given Fore, Aft, Exp format), then +> > -- adds the Name. +> > -- Examples +> > -- 300.50*kg 50.0/ms 1.0*rad 5.0 (dimensionless) +> +> I am now using the "Middle_Dot" character by default, rather than "*", +> for product units, so we might want to use that here as well. Alternatively, +> why not just use one space? That seems to be what the SI style +> guide recommends. It is no particular problem to look ahead +> across a space to see if there is a unit name following. + +But I believe it could pose a considerable problem to decide whether what is +following the space is indeed a unit or something else and accordingly has to +be consumed or not. + +Assume you are expecting a mass in;ut and have read "5 ". Next you expect +unit "g" possibly prefixed by 'k' or some such. + +Next character is indeed a 'g', but what you are actually reading is a string +like "5 goats". Where do you stop reading and what do you return? Do you +return "5 g" and "oats" left in the input stream or do you raise an exception? + +It's much easier if you insist in a character being present. Then "5 goats" +is easy to distinguish from "5*g" or even the nonsense "5*goats". + +If you don't like the '*' or middle-dot here, I'd vote for no character in +between like "5g". + +**************************************************************** + +From: Stephen Leake +Sent: Tuesday, February 11, 2003 11:21 AM + +> > Thank you again for your comments. I am not a practicing +> > physicist or chemist, so I have only distant memories +> > of using these units from my college days. Please have +> > patience with my mistakes ;-). +> +> No problem - it's just that what I'm after, try to practice physics +> with this proposal and see how it works out. I'm full of doubt. Ask +> physicists in the Ada community to use it on their problems at hand. + +Ok, I'll bite. I do satellite simulations for NASA Goddard. + +I've played with putting units into Ada in the past. + +My main applications use Cartesian vectors, in meters, meters/sec, +etc. All normal vector operations must be supported; multiply by +scalar, dot and cross product, multiply by matrix, etc. No +compile-time units package comes close to doing that; it's +combinatoric in both the units themselves and the dimension of the +vector or matrix! + +In my experience, a good test finds units problems very easily, so +it's a waste of time trying to get the compiler to do it. + +> > My recommendation is that users would instantiate the +> > Multiplicative_Operators package themselves, and only as +> > necessary for their particular usage pattern. Lettings users +> > do their "own" instantiations is safe, because the units are checked +> > on each instantiation using the "Assert." +> +> The last sentence may be true, but it is still impractical. This is +> my strongest objection against this method. + +I agree with this; requiring users to figure out which generics to +instantiate and with is too high a cost for the gain of checked units. + +In addition, if you are relying on a run-time Assert to enforce +safety, you might as well go whole-hog and use the attributes +approach. I have not closely examined Christoph's latest effort in +this area, but it sounds promising; use the run-time attributes +approach for full checking, but then make a small code change to do no +checking in a release version. + +However, I currently think a better place to do units is in SPARK, or +some similar semantics checker, not at compile time or run time. It +should be simple to build an ASIS tool that uses structured comments +on variables and parameters to check units. + +**************************************************************** + +From: Christoph Grein +Sent: Wednesday, February 19, 2003 3:11 AM + +OK, this is just one other opinion, so it does not make a good statistic ... +but it exactly reflects mine. + +> ... +> In my experience, a good test finds units problems very easily, so +> it's a waste of time trying to get the compiler to do it. + +Exactly, and also code walk-thru's. + +> > > My recommendation is that users would instantiate the +> > > Multiplicative_Operators package themselves, and only as +> > > necessary for their particular usage pattern. Lettings users +> > > do their "own" instantiations is safe, because the units are checked +> > > on each instantiation using the "Assert." +> > +> > The last sentence may be true, but it is still impractical. This is +> > my strongest objection against this method. +> +> I agree with this; requiring users to figure out which generics to +> instantiate and with is too high a cost for the gain of checked units. + +I also believe that the presence of any such method must be (nearly) +completely unperceptible as long as there are no dimension errors. + +As I said in my first very message, the problmes in big software projects +are buried at other places, e.g. missing analysis before reuse (see Ariane 5), +incomplete interface documents that do not specify the units (see Mars Lander). +This method would not have prevented Mars Lander's failure. + +**************************************************************** +

Questions? Ask the ACAA Technical Agent