!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