Previous: 2.5.2 Simple Compiler Directives
Up: 2.5 More C Statements
Next: A Simple Loop --- while
Previous Page: 2.5.2 Simple Compiler Directives

2.5.3 More on Expressions

Expressions used for computation or as conditions can become complex, and considerations must be made concerning how they will be evaluated. In this section we look at three of these considerations: precedence and associativity, the data type used in evaluating the expression, and logical operators.

Precedence and Associativity

Some of the assignment statements in the last section included expressions with more than one operator in them. The question can arise as to how such expressions are evaluated. Whenever there are several operators present in an expression, the order of evaluation depends on the precedence and associativity (or grouping) of operators as defined in the programming language. If operators have unequal precedence levels, then the operator with higher precedence is evaluated first. If operators have the same precedence level, then the order is determined by their associativity. The order of evaluation according to precedence and associativity may be overridden by using parentheses; expressions in parentheses are always evaluated first.

Table 2.3 shows the arithmetic and relational operators in precedence level groups separated by horizontal lines. The higher the group in the table, the higher its precedence level. For example, the precedence level of the binary operators *, /, and % is the same but it is higher than that of the binary operator group +, -. Therefore, the expression

x + y * z
is evaluated as
x + (y * z)

Associativity is also shown in the table. Left to right associativity means operators with the same precedence are applied in sequence from left to right. Binary operators are grouped from left to right, and unary from right to left. For example, the expression

x / y / z
is evaluated as
(x / y) / z

The precedence of the relational operators is lower than that of arithmetic operators, so if we had an expression like

x + y >= x - y
it would be evaluated as
(x + y) >= (x - y)
However, we will often include the parentheses in such expressions to make the program more readable.

From our payroll example, consider the assignment expression:

overtime_pay = OT_FACTOR * rate_of_pay * (hours_worked - REG_LIMIT);
In this case, the parentheses are required because the product operator, *, has a higher precedence than the sum operator. If these parentheses were not there, the expression would be evaluated as:
overtime_pay = (((OT_FACTOR * rate_of_pay) * hours_worked) - REG_LIMIT);
where what we intended was:
overtime_pay = ((OT_FACTOR * rate_of_pay) * (hours_worked - REG_LIMIT));
That is, the subtraction to be done first, followed by the product operators. There are several product operators in the expression; they are evaluated left to right in accordance with their associativity. Finally, the assignment operator, which has the lowest precedence, is evaluated.

Precise rules for evaluating expressions will be discussed further in Chapter where a complete table of the precedence and associativity of all C operators will be given. Until then, we will point out any relevant rules as we need them and we will frequently use parentheses for clarity.

Data Types in Expressions

Another important consideration in using expressions is the type of the result. When operands of a binary operator are of the same type, the result is of that type. For example, a division operator applied to integer operands results in an integer value. If the operands are of mixed type, they are both converted to the type which has the greater range and the result is of that type; so, if the operands are int and float, then the result is floating point type. Thus, is 1 and is 1.6. The C language will automatically perform type conversions according to these rules; however, care must be taken to ensure the intent of the arithmetic operation is implemented. Let us look at an example.

Suppose we have a task to find the average for a collection of exam scores. We have already written the code which sums all the the scores into a variable total_scores and counted the number of exams in a variable number_exams. Since both of these data items are integer values, the variables are declared as type int. The average, however is a real number (has a fractional part) so we declared a variable average to be of type float. So we might write statements:

int total_scores, number_exams;
     float  average;

...

average = total_scores / number_exams;

in our program. However, as we saw above, since total_scores and number_exams are both integers, the division will be done as integer division, discarding any fractional part. C will then automatically convert that result to a floating point number to be assigned to the variable average. For example, if total_scores is 125 and number_exams is 10, the the right hand side evaluates to the integer 12 (the fractional part is truncated) which is then converted to a float, 12.0 when it is assigned to average. The division has already truncated the fractional part, so our result will always have 0 for the fractional part of average which may be in error. We could represent either total_scores or number_exams as float type to force real division, but these quantities are more naturally integers. We would like to temporarily convert one or both of these values to a real number, only to perform the division. C provides such a facility, called the cast operator. In general, the syntax of the cast operator is: which converts the value of <expression> to a type indicated by the <type-specifier>. Only the value of the expression is altered, not the type or representation of the variables used in the expression. The average is then computed as:
average = (float) total_scores / (float) number_exams;
The values of the variables are first both converted to float (e.g. 125.0 and 10.0), the division is performed yielding a float result (12.5) which is then assigned to average. We cast both variables to make the program more understandable. In general, it is good programming practice to cast variables in an expression to be all of the same type. After all, C will do the cast anyway, the cast is simply making the conversion clear in the code.

Logical Operators

It is frequently necessary to make decisions based on a logical combination of True and False values. For example, a company policy may not allow overtime pay for highly paid workers. Suppose only those workers, whose rate of pay is not higher than a maximum allowed value, are paid overtime. We need to write the pay calculation algorithm as follows:

if ((hours_worked > REG_LIMIT) AND (rate_of_pay <= MAXRATE))
          calculate regular and overtime pay
     else
          calculate regular rate pay only, no overtime.

If hours worked exceeds the limit, REG_LIMIT, AND rate of pay does not exceed MAXRATE, then overtime pay is calculated; otherwise, pay is calculated at the regular rate. Such logical combinations of True and False values can be performed using logical operators. There are three generic logical operators: AND, OR, and NOT. Symbols used in C for these logical operators are shown in Table 2.4 Table 2.5 shows logical combinations of True and False values and the resulting values for each of these logical operators. We have used T and F for True and False in the table. From the table we can see that the result of the AND operation is True only when the two expression operands are both True; the OR operation is True when either or both operands are True; and the NOT operation, a unary operator, is True when its operand is False.

We can use the above logical operators to write a pay calculation statement in C as follows:

if ((hours_worked > REG_LIMIT) && (rate_of_pay <= MAXRATE)) {
          regular_pay = REG_LIMIT * rate_of_pay;
          overtime_pay = OT_FACTOR * rate_of_pay *
                              (hours_worked - REG_LIMIT);
     }
     else {
          regular_pay = hours_worked * rate_of_pay;
          overtime_pay = 0;
     }
(We assume that MAXRATE is defined using a define directive). We use parentheses to ensure the order in which expressions are evaluated. The expressions in the innermost parentheses are evaluated first, then the next outer parentheses are evaluated, and so on. If (hours_worked > REG_LIMIT) is True AND (rate_of_pay <= MAXRATE) is True, then the whole if expression is True and pay is calculated using the overtime rate. Otherwise, the expression is False and pay is calculated using regular rate.

In C, an expression is evaluated for True or False only as far as necessary to determine the result. For example, if (hours_worked > REG_LIMIT) is False, the rest of the logical AND expression need not be evaluated since whatever its value is, the AND expression will be False.

A logical OR applied to two expressions is True if either expression is True. For example, the above statement can be written in C with a logical OR operator, ||.

if ((hours_worked <= REG_LIMIT) || (rate_of_pay > MAXRATE)) {
          regular_pay = hours_worked * rate_of_pay;
          overtime_pay = 0;
     }
     else {
          regular_pay = REG_LIMIT * rate_of_pay;
          overtime_pay = OT_FACTOR * rate_of_pay *
                              (hours_worked - REG_LIMIT);
     }
If either hours worked does not permit overtime OR the rate exceeds MAXRATE for overtime, calculate regular rate pay; otherwise, calculate regular and overtime pay. Again, if (hours_worked <= REG_LIMIT) is True, the logical OR expression is not evaluated further since the result is already known to be True. Precedence of logical AND and OR operators is lower than that of relational operators so the parentheses in the previous two code fragments are not required; however, we have used them for clarity.

Logical NOT applied to a True expression results in False, and vice versa. We can rewrite the above statement using a logical NOT operator, !, as follows:

if ((hours_worked > REG_LIMIT) && !(rate_of_pay > MAXRATE)) {
          regular_pay = REG_LIMIT * rate_of_pay;
          overtime_pay = OT_FACTOR * rate_of_pay *
                              (hours_worked - REG_LIMIT);
     }
     else {
          regular_pay = hours_worked * rate_of_pay;
          overtime_pay = 0;
     }
If hours worked exceed REG_LIMIT, AND it is NOT True that rate of pay exceeds MAXRATE, then calculate overtime pay, etc. The NOT operator is unary and its precedence is higher than binary operators; therefore, the parentheses are required for the NOT expression shown.



Previous: 2.5.2 Simple Compiler Directives
Up: 2.5 More C Statements
Next: A Simple Loop --- while
Previous Page: 2.5.2 Simple Compiler Directives

tep@wiliki.eng.hawaii.edu
Tue Aug 16 14:01:55 HST 1994