## 4.4 Predicates

To this point, little regard has been paid to the documentation of procedures. Comments have been included where appropriate, but nothing has been said about any special need to document the role and function of procedures. This needs attention, for procedures are not just portions of a program, or miniature programs on their own, but are pieces of re-usable code that may be called upon under a variety of circumstances. It is important therefore, that each procedure be clearly documented as to:

• its purpose
• any assumptions it makes about the data passed to it (preconditions)
• the nature of any data produced by it (postconditions)

Such documentation will allow the programmer to avoid errors caused by using a poorly remembered piece of code for some purpose for which it was never intended; the proper use will be outlined in comments that remain with the code. Consider, for instance, a procedure designed to compute what percentage one number is of another:

PROCEDURE Percent (num, denom : REAL; VAR result : REAL);

BEGIN
result := 100.0 * num / denom;
END Percent;

What will happen if this code is called with denom equalling zero? There are two ways to avoid the divide-by-zero run-time error lurking in indiscriminate use of this procedure. The first is to be up-front about the limitations of the procedure by documenting them:

PROCEDURE Percent (num, denom : REAL; VAR result : REAL);
(* calculates what percentage num is of denom *)
(* pre : denom # 0.0
post: result = 100.0 * num / denom *)

BEGIN
result := 100.0 * num / denom;
END Percent;

If the procedure is written this way, it becomes the responsibility of the portion of code invoking it to check that the actual parameter passed to Percent is nonzero before going ahead with the procedure.

On the other hand, the responsibility for avoiding a run time error could be given to the procedure itself. If this is done, the main program must be informed when the result is not valid. The procedure could be written:

PROCEDURE Percent (num, denom : REAL;
VAR result : REAL; VAR ok : BOOLEAN);
(* calculates what percentage num is of denom *)
(* pre : none
post: if denom # 0.0
then result = 100.0 * num / denom and ok = true
else result is undefined and ok is false *)

BEGIN
IF denom # 0.0
THEN
result := 100.0 * num / denom;
ok := TRUE
ELSE
ok := FALSE
END
END Percent;
Preconditions and postconditions specified in the documentation of a procedure are called predicates.

### Example:

Write a program module that can examine two lines, each determined from the coordinates of two points entered in from the keyboard, and determine whether the lines are parallel, perpendicular, or neither.

### Discussion:

When two points are available in coordinate form P1(x1, y1) and P2(x2, y2), the slope of the line through the two points is given by



Two lines are parallel if they have the same slope, and they are perpendicular if their slopes are negative reciprocals. A horizontal line has zero slope, and the slope of a vertical line is undefined (it has no slope.) Thus, the failure of a procedure Slope to compute a result is not a bad thing; it just indicates that the line in question is vertical.

MODULE Slopes;

(* Written by R.J. Sutcliffe *)
(* using P1 MPW Modula-2 for the Macintosh computer *)
(* last revision 1991 02 26 *)

FROM STextIO IMPORT
FROM SRealIO IMPORT
FROM SIOResult IMPORT

VAR
line1X1, line1Y1, line1X2, line1Y2,
line2X1, line2Y1, line2X2, line2Y2,
line1Slope, line2Slope: REAL;
line1Ok, line2Ok : BOOLEAN;
key : CHAR;

PROCEDURE GetNum (VAR theNum : REAL);
(* gets a number input from the keyboard
Pre: none
Post: if ok is true then return
else ask for input to be re-entered *)

VAR
ok : BOOLEAN;

BEGIN
REPEAT
WriteString ("Type the number here ==> ");
SkipLine;
IF NOT ok (* use saved value for testing *)
THEN
WriteLn;
WriteString ("error in input number; try again.");
WriteLn;
END;
UNTIL ok;
END GetNum;

PROCEDURE Slope (x1, y1, x2, y2: REAL;
VAR m: REAL; VAR ok: BOOLEAN);
(* computes the slope of a line joining (x1, y1) and (x2, y2)
Pre: none
Post: if x1#x2 then m is the slope and ok is true
else m is set to the maximum possible real; ok = false *)

VAR
deltaX, deltaY : REAL;

BEGIN
deltaX := x2 - x1;
deltaY := y2 - y1;
IF deltaX # 0.0
THEN
m := deltaY/deltaX;
ok := TRUE;
ELSE
m := MAX (REAL);
ok := FALSE;
END;

END Slope;

BEGIN    (* main program *)
WriteString ("This program computes the slopes of 2 lines");
WriteString (" from their coordinates.");
WriteLn;
WriteLn;
(* obtain the coordinates *)
WriteString ("First line:");
WriteLn;
WriteString ("First point:");
WriteString ("x = ");
GetNum (line1X1);
WriteString ("y = ");
GetNum (line1Y1);
WriteString ("Second point:");
WriteString ("x = ");
GetNum (line1X2);
WriteString ("y = ");
GetNum (line1Y2);
WriteString ("Second line:");
WriteLn;
WriteString ("First point:");
WriteString ("x = ");
GetNum (line2X1);
WriteString ("y = ");
GetNum (line2Y1);
WriteString ("Second point:");
WriteString ("x = ");
GetNum (line2X2);
WriteString ("y = ");
GetNum (line2Y2);

(* compute the slopes *)
Slope (line1X1, line1Y1, line1X2, line1Y2, line1Slope, line1Ok);
Slope (line2X1, line2Y1, line2X2, line2Y2, line2Slope, line2Ok);

(* now output the data. *)
WriteString ("The slope of the first line is ");
IF line1Ok
THEN
WriteFixed (line1Slope, 6, 0)
ELSE
WriteString ("undefined.")
END;
WriteLn;
WriteString ("The slope of the second line is ");
IF line2Ok
THEN
WriteFixed (line2Slope, 6, 0)
ELSE
WriteString ("undefined.")
END;
WriteLn;
WriteString ("The 2 lines are ");
IF NOT (line1Ok OR line2Ok) OR (line1Slope = line2Slope)
THEN
WriteString ("parallel.");
ELSIF((line1Slope = 0.0) AND NOT line2Ok) OR
((line2Slope = 0.0) AND NOT line1Ok) OR
(ABS (line1Slope  + (1.0 / line2Slope)) < 0.000001) THEN
WriteString ("perpendicular.");
ELSE
WriteString ("neither parallel nor perpendicular.")
END;

WriteLn;
WriteString ("Press a key to continue ==>");

END Slopes.

NOTES: 1. MAX is a new standard identifier. It returns the maximum value of a type. This has not been done in this case to simulate a value of "infinity," but so that a line with no slope has some value attached to it for purposes of comparison.

2. Observe the comparison (ABS (line1Slope + (1.0 / line2Slope)) < 0.000001). It would be easier to write (line1Slope = - (1.0 / line2Slope)). However, this is unlikely to work in practice because of rounding off effects, so one must instead rely on the two values being close.

3. Observe also the order of checking for perpendicularity. If either of the first two conditions is true, the third will not be checked--a good thing if it could produce a divide-by-zero error.

Here is a single run from this module:

This program computes the slopes of 2 lines from their coordinates.

First line:
First point:x =  please type here ===> 1.0
y =  please type here ===> 3.0
Second point:x =  please type here ===> 3.0
y =  please type here ===> 8.0
Second line:
First point:x =  please type here ===> 2.0
y =  please type here ===> 4.0
Second point:x =  please type here ===> 7.0
y =  please type here ===> 2.0
The slope of the first line is 2.500000
The slope of the second line is -0.4000000
The 2 lines are perpendicular.
Press a key to continue ==>n

Contents