Important: The information in this document is obsolete and should not be used for new development.
Implementing an Expression Evaluation Method
Though PowerPC Numerics can recommend certain expression evaluation methods, these methods must be implemented by the compiler. As described in Chapter 3, "Expression Evaluation," compilers may or may not support widest-need evaluation. This section describes
- the advantages and disadvantages of supporting and not supporting widest-need evaluation
- some special issues compilers must consider regarding evaluating floating-point constants and initializing floating-point variables
- the FPCE-recommended macros and pragmas that help programmers use the most efficient types possible and determine which expression evaluation method is being used
Expression Evaluation Without Widest Need
The main advantage of using an expression evaluation method without widest-need evaluation is that it is simple to implement. The PowerPC architecture is based on single-precision and double-precision operations, so either single or double is a logical choice for the minimum evaluation format.Choosing single as the minimum format provides the highest performance for single-precision algorithms yet still allows double and double-double algorithms to be performed with greater precision and range. A single minimum evaluation format, then, allows the best possible performance for all expressions by allowing the semantic type of a simple expression to determine its evaluation format.
Choosing double as the minimum format provides extra precision and range to single-precision operations and conforms to the traditional behavior of the C programming language (traditional C performs all floating-point operations in double precision). Performing all single-precision operations in double precision protects the operations against roundoff errors and against encountering an overflow or underflow in an intermediate value. For example, consider the following expression:
If you perform this expression by hand, you get . If all constants are in single format, the expression produces + . The constant is near the end of the range of single format. Multiplying by produces , which is rounded to + . Then, + is divided by , and the answer is still + .
If the minimum evaluation format is double, the constants and are converted to double format before the result is calculated. The multiplication operation no longer overflows the range of the data type because the double format can easily hold . The value divided by produces , which is then converted back to single format.
Choosing the double-double format provides the greatest available precision to all floating-point operations, protecting double-precision operations as well as single-precision operations from roundoff errors. However, it significantly decreases performance for those expressions that would normally be evaluated in a narrower format. In most cases, the extra precision is not necessary.
Imposing a narrow format allows the best possible performance for narrow-format operations but might produce more roundoff errors in places where the extra precision really is necessary. Using widest-need evaluation for complex expressions in conjunction with a minimum evaluation format minimizes the disadvantages of choosing one minimum evaluation format.
Expression Evaluation With Widest Need
Widest-need evaluation provides some of the advantages of using double-double as the minimum format while eliminating the pitfalls. With widest-need evaluation, if an expression contains a double-double variable, all other variables in that expression will ultimately be converted to double-double format, thus reducing the chance of roundoff error in these expressions. If an expression does not contain a double-double variable, widest-need evaluation allows the expression to be evaluated in the narrowest format possible, allowing the best possible performance for that expression.Widest-need evaluation can seriously inhibit the common subexpression removal optimization for subexpressions of narrower types. If the type of a subexpression is narrower than the type of its enclosing expression, the format of the enclosing expression is imposed on that subexpression. The subexpression's operands are converted to the wider format. Because the conversion must occur as if at run time, the common subexpression removal optimization is in effect disabled for this subexpression.
Floating-Point Constant Evaluation
When a floating-point constant expression appears in a program, the expression evaluation method determines its evaluation format. When widest-need evaluation is not used, the constant is the wider of the minimum evaluation format and the semantic type of the expression. With widest-need evaluation in effect, the constant is converted to the evaluation format of the complex expression it is part of.In most cases, floating-point constant expressions must be evaluated as if at run time, although they may actually be evaluated at compile time. At compile time, the default rounding direction is in effect, and no floating-point exceptions may be flagged. (These conditions are known as the default floating-point environment. See Chapter 4, "Environmental Controls," for more information.) However, if evaluation takes place as if at run time, the floating-point environment may affect or be affected by the evaluation. This means that if an expression is unexceptional and the default rounding direction is in effect, the expression can be evaluated at compile time. If the expression is exceptional or the current environment is not in the default state, the expression must be evaluated at run time.
In the following two cases the evaluation always takes place at compile time:
The requirement that floating-point constant expressions be evaluated as if at run time usually inhibits the constant folding optimization, in which values of constants are combined at compile time to produce fewer operations at run time. However, constant folding can occur
- The constant expression appears within the declaration of a variable explicitly declared to be static:
static double x = 0.3 + 0.3;- The constant expression appears within the declaration of an aggregate type variable (array, structure, or union):
struct {int x = 0; double y = 0.3 + 0.3;} numbers;
The following example illustrates when floating-point constant expressions are evaluated:
- if a floating-point constant expression is required to be evaluated at compile time (that is, if the expression is part of the declaration of either an explicitly declared static variable or an aggregate type)
- if the evaluation of the expression at compile time has exactly the same results as it would if evaluated at run time. This can happen under the following conditions:
- If an expression evaluates to be nonexceptional at compile time, it would also evaluate to be nonexceptional at run time.
- If the expression appears in a portion of the program where access to the floating-point environment is disabled, the default environment will be in effect at run time, just as it is at compile time.
#pragma fenv_access on void f(void) { float w[] = {0.0 / 0.0}; /* no exception raised */ static float x = 0.0 / 0.0; /* no exception raised */ float y = 0.0 / 0.0; /* exception raised */ x = 1.0 / 4.0; /* exact (no exception raised) */ y = 1.0 / 3.0; /* exception raised */ } #pragma fenv_access off void g(void) { double z; z = 0.0 / 0.0; /* no exception raised */ }In the declaration of the arrayw
, a floating-point constant expression contains division by zero. This operation is evaluated at compile time because it appears in the declaration of an aggregate type. Similarly, the division by zero in the declaration ofx
is evaluated at compile time because it is declared static. Neither of these expressions generates an exception, because they occur at compile time, although the compiler should generate a warning message in each case.The next declaration (of
float y
) also includes the expression . This expression is evaluated at run time, and the invalid-operation exception is raised.The first statement in function
f
assigns tox
the value of the floating-point constant expression . The compiler looks at this expression to determine if it will raise any exceptions. The expression is found to be exact, so the compiler can optimize it.The second statement of the function
f
assigns toy
the value of the floating-point constant expression . The compiler determines that this expression will raise the inexact exception, so it must be evaluated at run time. The compiler cannot optimize it.Finally, function
g
assigns to the double variablez
the value of the floating-point constant expression . This statement appears after thefenv_access
pragma has been turned off. This pragma (described in the section "Environmental Access Switch" on page D-1) signals to the compiler that the default environment will be in effect at run time. Because exceptions are disabled in the default environment, this statement will not raise a run-time exception, and so it may be evaluated at compile time and optimized.Initializing Floating-Point Objects
A program achieves better performance if it initializes data (including floating-point data) at compile time. The degree to which this is possible depends on the programming language and the compiler options that are supported.As specified for the C programming language, floating-point constant expressions are generally evaluated as if at run time. This includes floating-point constants that initialize floating-point variables. However a floating-point variable may be initialized at compile time
For programming languages other than C, the data initialization model may be simpler. For example, in Fortran static initialization is accomplished with the DATA statement (embedded in a BLOCK DATA subprogram for labeled COMMON initialization), and the initializing values may only be constants or parameters. Such initialization is accomplished as if at compile time. Variables not initialized by the DATA statement are considered uninitialized and are assigned values at execution time with executable statements.
- if the variable is declared to be static
static float x = 0.3;- if the variable is part of an aggregate type
struct {int x = 0; float y = 0.3;} numbers;- if the initializing value is nonexceptional (exact) and is in the format of the variable
double y = 0.0;
float x = 0.0f;- if access to the floating-point environment is disabled in the part of the program where the variable is initialized
#pragma fenv_acess off
float x = 0.3;
Data initialization rules for Pascal compilers are implementation defined and must be fully documented. In MPW Pascal targeting 680x0-based Macintosh computers, for example, a unit requiring initialization of its data declares a public procedure, called at execution time by the host program, that performs the initialization. Apple II Pascal, on the other hand, supports an initialization section within the unit.
Compiler Extensions for Expression Evaluation
The FPCE technical report recommends that compilers implement two macros that help a programmer determine which expression evaluation method is being used and three pragmas that help a programmer use the most efficient data type for functions.Determining the Expression Evaluation Method
Two macros that characterize the evaluation method for floating-point expressions may be defined in thefloat.h
header file. The macro_MIN_EVAL_FORMAT
tells which numeric data format is used as the minimum evaluation format:
0 float
(single)1 double
2 long double
(double-double)The macro
_WIDEST_NEED_EVAL
specifies if widest-need evaluation is performed:
0 no 1 yes Widening for Efficiency
In general, programmers want to use the most efficient floating-point data type for the architecture on which their applications will run. If the application is to run on more than one architecture, you cannot guarantee that the most efficient type on one architecture will be the most efficient type for the others. The FPCE technical report recommends three preprocessor pragmas to facilitate running the same application efficiently on different architectures. When these pragmas are turned on, the compiler uses the wider of the architecture's most efficient type and the declared type for any function, parameter, or local variable declared after the pragma.
#pragma fp_wide_function_returns on | off #pragma fp_wide_function_parameters on | off #pragma fp_wide_variables on | offIf the first pragma,fp_wide_function_returns
, is turned on in a module, all of the functions defined below the pragma will have return values in the most efficient data type for the architecture if it is wider than the declared return type. If the following example is compiled for the 680x0 architecture, both functionsffunc
andldfunc
return typelong double
. If compiled for the PowerPC architecture,ffunc
returns typedouble
andldfunc
returns typelong double
(because data types may be widened to the most efficient type but not narrowed).
#pragma fp_wide_function_returns on float ffunc (float f) { /* code for ffunc */ } long double ldfunc (double y) { /* code for ldfunc */ }If the second pragma,fp_wide_function_parameters
, is turned on in a module, all of the parameters for all of the functions defined below the pragma are converted to the most efficient data type for the architecture if it is wider than the declared types of the parameters. In the following example, the parametersx
andy
are both typedouble
on the PowerPC architecture and typelong double
on the 680x0 architecture. If an architecture's most efficient type wasfloat
, the types for both parameters would remain the same (because a parameter's type may be widened to the most efficient type but never narrowed).
#pragma fp_wide_function_parameters on float func(float x, double y) { /* code for func */ }If the third pragma,fp_wide_variables
, is turned on in a module, all local variables defined below the pragma are converted to the most efficient data type for the architecture if it is wider than the declared types of the variables. In the following example, the variablesz
andq
are both typedouble
on the PowerPC architecture and typelong double
on the 680x0 architecture. If an architecture's most efficient type wasfloat
, the types for both variables would remain the same (because a variables's type may be widened to the most efficient type but never narrowed).
#pragma fp_wide_variables on float func(float x) { float z; double q; /* code */ }These pragmas can occur only outside external declarations. Each pragma remains in effect until it is explicitly turned off or until the end of the module. The default state for all three pragmas is off.If an address or
sizeof
operator is applied to a widened parameter or variable, a compile-time warning is issued. Casts avoid widening in areas where one of these pragmas is turned on.