!topic LSN028 on Exact evaluation of static expressions $Revision: $
!reference MS-4.9(1);4.0
!from Tucker Taft $Date: $
!discussion
In the recent "zero-based" budget for Ada 9X, we have
marked item #44 "Uniform rules for static evaluation (always exact)"
as "Important" rather than essential. However, we would
like to provide some rationale for keeping this proposal,
so it can be better evaluated.
In Ada 83, static evaluation may be performed exactly, and
for static expressions of type universal-int and universal-real,
exact evaluation is required. In most compilers with which
we are familiar, all static (compile-time) evaluation is
performed exactly, because the compilers are designed
to be at least somewhat rehostable and/or retargetable,
and it is simply easier to use exact arithmetic
than to simulate (unknown) target hardware with (unknown) host hardware.
Furthermore, since all universal static expressions require
exact evaluation, all of the machinery is in place to do the
exact evaluation, and target simulation is more work.
For Ada 9X, there has been a general desire to "tighten up" the
numeric model, and at least come closer to describing the actual
hardware with the model attributes. There was some consideration of
going all the way to a fully "deterministic" model like LCAS for Ada 9X,
and we have tried to ensure that the attributes necessary for
LCAS will be in place to allow implementations to conform to LCAS
by providing suitable documentation.
We ultimately have decided to stay with the less deterministic model,
because of the issues of extended-precision intermediates.
In any case, any attempt to improve the "determinacy" of the model
requires making some decision about the characteristics
of static evaluation. It is clearly more work for implementors
to perfectly simulate target hardware arithmetic. The only other
option that would provide determinacy is to specify exact
evaluation.
In addition to the determinacy concerns, there are also usability
arguments for making all static evaluations exact (and non-overflowing).
Here are some examples (we use the term "XYZ" compiler for a compiler
that uses target arithmetic for static evaluation):
type Int_32 is range -2**31 .. 2**31-1;
-- This is a portable way to get a 32-bit (2's complement) integer.
. . .
X : Int_32 := -2**31; -- This works on almost all compilers.
-- However, on an XYZ compiler, this
-- raises constraint error because target rather
-- than infinite-precision evaluation is used.
Of course, in this case, it could be rewritten to use "Int_32'FIRST."
But since almost no compilers complain about this, it is common
to see such code in supposedly "portable" Ada programs,
and in some contexts, it is in fact easier to read with this static
expression rather than the attribute reference.
However, when this expression hits an XYZ compiler, a run-time constraint
error occurs (usually with a compile-time warning as well).
This is an example where a modest amount of effort by the XYZ implementors
could increase the portability of all Ada code.
Here is another case:
the expression: 1.0 ** (-5) is always equivalent to: 0.00001
in compilers that use exact arithmetic for all static calculations,
whereas for the XYZ compilers, these could produce different answers.
Also, "X := FLOAT(1.0 ** (-5));" is equivalent to "X := 1.0 ** (-5);"
(presuming X is of type FLOAT) only when exact arithmetic is used
for static calculations.
These equivalences seems highly desirable, and are provided by
most compilers.
Note that static expressions of *any* real type are permitted in
several places where the exact value of the expression is quite
relevant. In particular, in the delta and small (RM 13.2(12))
specifications on a fixed-point type definition, and in the range
specification on a float or fixed-point type definition, a static
expression of any real type is permitted. For the small specification
in particular, whether (1.0 ** (-5)) is calculated exactly or
approximately can make a major semantic difference later
(and may even affect legality depending on what smalls are allowed).
Finally, from a consistency point of view, it works better
if static evaluation of all numeric types is exact:
Non-universal type Universal type
Literals exact exact
Static exact exact
expressions
Dynamic inexact inexact
expressions
on
In a compiler that uses exact arithmetic for all static evaluations,
The expressions initializing X and Y.
X and Y end up with the same answer on both the left and the right
(presuming the rounding mode does not waiver back and forth .
In an XYZ compiler, X and Y don't necessarily get the same answer.
the left side and the right side are
Here is a little table that summarizes