The GNAT compiler now supports dimensionality checking. The user can specify physical units for objects, and the compiler will verify that uses of these objects are compatible with their dimensions, in a fashion that is familiar to engineering practice. The dimensions of algebraic expressions (including powers with static exponents) are computed from their consistuents.
This feature depends on Ada 2012 aspect specifications, and is available from version 7.0.1 of GNAT onwards. The GNAT-specific aspect Dimension_System allows you to define a system of units; the aspect Dimension then allows the user to declare dimensioned quantities within a given system.
The major advantage of this model is that it does not require the declaration of multiple operators for all possible combinations of types: it is only necessary to use the proper subtypes in object declarations.
The simplest way to impose dimensionality checking on a computation is to make use of the package System.Dim.Mks, which is part of the GNAT library. This package defines a floating-point type MKS_Type, for which a sequence of dimension names are specified, together with their conventional abbreviations. The following should be read together with the full specification of the package, in file s-dimmks.ads.
type Mks_Type is new Long_Long_Float with Dimension_System => ( (Unit_Name => Meter, Unit_Symbol => 'm', Dim_Symbol => 'L'), (Unit_Name => Kilogram, Unit_Symbol => "kg", Dim_Symbol => 'M'), (Unit_Name => Second, Unit_Symbol => 's', Dim_Symbol => 'T'), (Unit_Name => Ampere, Unit_Symbol => 'A', Dim_Symbol => 'I'), (Unit_Name => Kelvin, Unit_Symbol => 'K', Dim_Symbol => "Theta"), (Unit_Name => Mole, Unit_Symbol => "mol", Dim_Symbol => 'N'), (Unit_Name => Candela, Unit_Symbol => "cd", Dim_Symbol => 'J'));
The package then defines a series of subtypes that correspond to these conventional units. For example:
subtype Length is Mks_Type with Dimension => (Symbol => 'm', Meter => 1, others => 0);
and similarly for Mass, Time, Electric_Current, Thermodynamic_Temperature, Amount_Of_Substance, and Luminous_Intensity (the standard set of units of the SI system).
The package also defines conventional names for values of each unit, for example:
m : constant Length := 1.0; kg : constant Mass := 1.0; s : constant Time := 1.0; A : constant Electric_Current := 1.0;
as well as useful multiples of these units:
cm : constant Length := 1.0E-02; g : constant Mass := 1.0E-03; min : constant Time := 60.0; day : constant TIme := 60.0 * 24.0 * min; ...
Using this package, you can then define a derived unit by providing the aspect that specifies its dimensions within the MKS system, as well as the string to be used for output of a value of that unit:
subtype Acceleration is Mks_Type with Dimension => ("m/sec^2", Meter => 1, Second => -2, others => 0);
Here is a complete example of use:
with System.Dim.MKS; use System.Dim.Mks; with System.Dim.Mks_IO; use System.Dim.Mks_IO; with Text_IO; use Text_IO; procedure Free_Fall is subtype Acceleration is Mks_Type with Dimension => ("m/sec^2", 1, 0, -2, others => 0); G : constant acceleration := 9.81 * m / (s ** 2); T : Time := 10.0*s; Distance : Length; begin Put ("Gravitational constant: "); Put (G, Aft => 2, Exp => 0); Put_Line (""); Distance := 0.5 * G * T ** 2; Put ("distance travelled in 10 seconds of free fall "); Put (Distance, Aft => 2, Exp => 0); Put_Line (""); end Free_Fall;
Execution of this program yields:
Gravitational constant: 9.81 m/sec^2 distance travelled in 10 seconds of free fall 490.50 m
However, incorrect assignments such as:
Distance := 5.0; Distance := 5.0 * kg:
are rejected with the following diagnoses:
Distance := 5.0; >>> dimensions mismatch in assignment >>> left-hand side has dimension [L] >>> right-hand side is dimensionless Distance := 5.0 * kg: >>> dimensions mismatch in assignment >>> left-hand side has dimension [L] >>> right-hand side has dimension [M]
The dimensions of an expression are properly displayed, even if there is no explicit subtype for it. If we add to the program:
Put ("Final velocity: "); Put (G * T, Aft =>2, Exp =>0); Put_Line ("");
then the output includes:
Final velocity: 98.10 m.s**(-1)