The four basic arithmetic operators, +, -, *, and /, can be used with any integer or floating point type. (We'll learn about more advanced types, such as fixed point types, later in the course.) The exponentiation operator, **, takes a base of type Integer or Float, and an exponent of type Integer. It returns the same type as the base. (If the base is Integer, the exponent may not be negative.)
In Ada 95, Float ** Float is part of the package Numerics.Elementary_Functions; that package also provides Exp, Log, and the trigonometric functions. See Annex A.5.1 in the Ada 95 RM for more details. Ada 83 compilers don't necessarily provide these functions, but some do provide them in a math package.
If we with and use a package containing Float ** Float, then our program can use ** between two Floats.
The six relational operators are these:
|<||less than||>||greater than||=||equal to|
|<=||less than or equal to||>=||greater than or equal to||/=||not equal to|
Five of these are the same as in Basic and Pascal, but the Ada symbol for "not equal to" is /=, because <> means box. (We'll discuss the "box" further when we cover array declarations and again when we cover generics.) The relational operators compare two objects of the same type, and return a result of type Boolean.
All of the above operators are used between two objects (e.g., B := A + B;). Of course, + and - may also be unary operators (B := +A; A := -A;).
The operators and, or, and xor are used between two Boolean objects, and not is a unary Boolean operator. Naturally, xor is the exclusive or: A xor B is True if either A or B is True, but not if both are True.
The numeric operator abs takes the absolute value of an Integer or Float. Since abs is a unary operator like -, we can write B := abs A;. We may, but don't have to, use parentheses and write B := abs(A);.
When used between two integers, / gives only the integer part of the quotient. Thus 11/4 is 2. The operators mod (modulo) and rem (remainder) are very similar to each other, and are used between two integers to give only the remainder in division. For example, 11 mod 4 is 3. For positive parameters, mod and rem are the same. But with negative parameters, mod gives the sign of the denominator while rem gives the sign of the numerator. Many programmers simply ignore rem and use mod exclusively.The operator & concatenates arrays, so we'll discuss it later. We'll learn that a String is one type of array (an array of Characters), so & can concatenate Strings.
The reserved word in tests if something is inside a range, and returns a Boolean result. For example, if Lower, Upper, and X are declared as Floats, we need not write
if X >= Lower and X <= Upper then ... end if;This can be expressed more clearly as
if X in Lower .. Upper then ... end if;
We can also write not in. Thus X not in Lower .. Upper is a clear way of expressing X < Lower or X > Upper.
A subtype may be substituted for the range to the right of in. If Day_Subtype is defined as a subtype of Integer, and I is an Integer, then we can write I in Day_Subtype.
QuestionIf we have the following declarations:
B : Boolean; A, X, Y : Character;then which one of the following is illegal?
- B := X <> Y;
You're right! Number 1 is illegal because <> should be /=.
- B := A in X .. Y;
No, number 2 is legal. The reserved word in tests to see if A is within the range X to Y. It returns a Boolean result, which is stored in B.
- B := A = X and X = Y;
No, number 3 is legal. Parentheses aren't necessary here, but it's the same as if we had written
B := (A = X) and (X = Y);In two places = returns a Boolean result. Then and takes the two Boolean results and also returns a result of type Boolean. That final result is then stored in B.
The Short Circuit Forms
Suppose that N (for numerator) and D (for denominator) are Floats, and we want to execute a block of code if the quotient is 10.0 or greater. We could write
if N/D >= 10.0 then -----; -----; (block of code) -----; end if;However, we realize that if D is 0.0, the program will raise a Constraint_Error or Numeric_Error trying to compute N/D. If the denominator is zero, we consider the quotient to be very large, so we want to execute the block of code when D is zero. Our second attempt might be
if D = 0.0 or N/D >= 10.0 then -----; -----; (block of code) -----; end if;
Here we hope that N/D won't be evaluated if D is zero. We figure that the compiler should be smart enough to know that if the expression before or is True, then the whole expression must be True, so the expression to the right of or needn't be evaluated. However, there's no guarantee that the compiler will write code to skip the evaluation of the second expression when the first is True. An optimizing compiler just might, for some unknown reason, even decide that the expression on the right should be evaluated first.
However, with the form or else, Ada can bypass (or "short circuit") evaluating the expression on the right when the one on the left is true:
if D = 0.0 or else N/D >= 10.0 then -----; -----; (block of code) -----; end if;
Ada guarantees that the expression to the left of or else will be evaluated first. If this expression is True, the entire expression must be True, and it's guaranteed that the second expression won't be evaluated. If the first expression is False, then of course the second expression must be evaluated to determine if the entire expression is True. Thus the code above will never try to divide by zero. If D is zero, the expression on the left is True. The expression on the right won't be evaluated in that case.
There's another "short circuit" form called and then. If the expression to the left of and then is False, the whole expression must be False, and the expression on the right won't be evaluated. If the expression on the left is True, then the expression on the right must be evaluated to determine the value of the entire expression. Suppose this time that we want to execute a block of code if N/D is less than 10.0. If the denominator is zero, we consider the quotient to be very large, and we don't want to execute the block of code in that case. We can write the following:
if D /= 0.0 and then N/D < 10.0 then -----; -----; (block of code) -----; end if;
Again this protects us from trying to divide by zero. If D is zero, the expression on the left is False. The second expression won't be evaluated in that case.
After we discuss arrays, we'll see how the short circuit forms can prevent us from using out-of-range subscripts. And when we learn about access types, we'll see how the short circuit forms can keep us from dereferencing a null access object ("pointer"). That means trying to access "the object pointed to" when there's no such object.
Note that Ada's and then is similar to && in C and C++, and Ada's or else is similar to || in C and C++.
Remember, and then evaluates the second expression only if the first expression is True; or else evaluates the second expression only if the first is False.
QuestionAssume that N has been declared Float, and that our program withs and uses a math package with a square root function called Sqrt. Naturally, we don't want to call Sqrt with a negative parameter. Which of the following would be more appropriate?
if N >= 0.0 and then Sqrt(N) > 1.618 then ...
You're right! We want the second expression to be evaluated only if the first expression is True, so we use and then.
if N >= 0.0 or else Sqrt(N) > 1.618 then ...
No, we want the second expression to be evaluated only if the first expression is True, so we use and then.