Some of the built-in procedures that have been used thus far can be employed in such a way that they return a value directly to the part of the expression that references them. For instance, programs in this text have contained things like:

ck :=CAP(ch); num1 :=ABS(num2); firstReal :=FLOAT(someInteger) + secondReal; m :=MAX(REAL);

and so on. (More of these will be covered in later chapters.) These have been referred to these all along as *functions* or, more correctly, as *function procedures*, but it should be clear that they work somewhat differently than do the regular procedures considered so far in this chapter, because they are oriented toward the return of a single result rather than to the taking of action. Moreover, that result is returned (in effect) in the procedure's own name, rather than in a variable parameter, as any result sent back by a regular procedure must be. The identifier of the procedure is itself being used in the expression as though it were a variable. Note the following definition:

Afunction procedureis a program unit that optionally accepts input data in its parameter list, but that always assigns an output value to the function name according to a fixed rule or formula determined by the statements in the procedure.

The usual mathematical notation for a function is y = f (x), where the *f* is the name of the function. In Modula-2 one would write *y := f (x)* to assign to *y* the results of calling the function procedure called *f* with the data *x*. This time the black box looks a little different, as shown in figure 4.6:

As has already been suggested, Modula-2 employs a variant of the PROCEDURE statement to implement functions. Here is the general form for a function procedure:

PROCEDUREname ( < formal parameter list > ) : <result type>; <local declarations>;BEGIN<statement sequence>;RETURNvalue; (* must be somewhere in body *)ENDname;

or, diagrammatically, as in figure 4.7:

There are four differences between function procedures and regular procedures:

*First*: the type of result to be returned must be specified by placing it after a colon following the closing parenthesis of the formal parameter list.

*Second*: a specific RETURN with the result (of the correct type) that is to be sent back must actually be executed somewhere in the body of the function procedure.

*Third*: the call of a function procedure must always include a parameter list, even if the latter is empty.

*Fourth*: the call of a function procedure appears in an expression, not as a separate command on a line by itself as does the call of a regular procedure.

These details are illustrated with some examples. First, here are some valid declarations:

PROCEDURELargest (firstReal, secondReal :REAL) :REAL;PROCEDUREStrange (VARfirstReal :REAL, secondReal :REAL) :CARDINAL; (* poor code *)PROCEDURERound (realToRound :REAL) :CARDINAL;PROCEDUREGetResult ( ) :CARDINAL;PROCEDURECAP(ch :CHAR) :CHAR;

The last of these is just the (implied) heading of one of the built-in pervasive function procedures. The second one does seem a little strange, because it returns both a variable parameter and a result type. In actual practice, it is not likely that anyone will do this very often--indeed, this practice may be legal, but it is very poor code. However, it does illustrate that function procedures are a specialization of regular procedures and share all the capabilities of the latter. To illustrate both this and the third one, here are two ways to write the code for a procedure that rounds off a REAL to the nearest whole number.

PROCEDURERound (num :REAL;VARnumOk :BOOLEAN) :CARDINAL;BEGIN(*poor version *)IF(num <=FLOAT(MAX(CARDINAL)))AND(num >=FLOAT(MIN(CARDINAL)))THENnumOk :=TRUE;RETURNTRUNC(num + 0.5);ELSEnumOk :=FALSE;RETURN0; (* got to send something back *)END; (* if *)ENDRound;

To use this version, one would write code like:

answer := Round (numToRound, success);IFNOTsuccessTHEN<take evasive action>END;

where, *numToRound* is REAL and *success* is a BOOLEAN included to ensure that all has gone well. This code is not safe, however, as the (possibly invalid) assignment to *answer* is made before checking to see if the procedure was successful. This is why function procedures should not be written with variable parameters. It is better to include preconditions in the procedure that are the responsibility of the main code to check before calling the function procedure:

PROCEDURERound (num :REAL) :CARDINAL; (* This procedure rounds off num to the nearest cardinal. Pre: (num <=FLOAT(MAX(CARDINAL)))AND(num >=FLOAT(MIN(CARDINAL))) Post: the cardinal returned is the closest cardinal to the real parameter supplied *)BEGINRETURNTRUNC(num + 0.5);ENDRound;

To use this version, one would write code like:

IF(numToRound <=FLOAT(MAX(CARDINAL)))AND(numToRound >=FLOAT(MIN(CARDINAL)))THENanswer := Round (numToRound);ELSE<take evasive action>END;

**NOTES**: 1. There can be as many RETURN statements as necessary. If the logic of the procedure allows any one of them to be encountered, the expression following the RETURN is evaluated and that value assigned to the procedure identifier. Control is immediately transferred to the END of the procedure.

2. If no RETURN <value> has been encountered before the END of the function procedure, a run-time error will be generated.

3. The standard function MIN, like MAX (discussed previously) returns the smallest value for whatever type is given as the parameter.

4. If one writes a function procedure with no parameters, it must be declared and referred to in any calls as *Nameofprocedure ()*, that is, with an empty parameter list.

Write a program that will reduce common fractions to lowest terms. The numerator and denominator should be input from the keyboard, and the result printed on the screen.

A fraction is reduced to lowest terms by dividing the numerator and denominator be the GCD of the two. In the last chapter, an algorithm was developed for computing the GCD, and this can become a procedure within the program to be presented here. The GCD program in turn contained a code to swap two numbers, and this too can be abstracted as a single entity and made a separate procedure.

In the following refinement therefore, the step of computing the GCD is not written out in detail but is conceptualized as a single step. However, there are some interesting possibilities that can be encountered when printing out the result, and these are detailed.

Ask the user for the numerator and denominator. Read them into cardinal variables. set a goodData flag if both are non-zero Compute and print the result as appropriate: if the data was good then compute the GCD of the two (use a procedure for this) divide the numerator and denominator by the GCD print the numerator if the denominator is not equal to one print a bar for the fraction print the denominator if the numerator was zero then, provided the denominator is not zero, just print a zero otherwise, say that the fraction is indeterminate if the denominator alone was zero say that the fraction is undefined exit the program

MODULEReduceFraction; (* This program reduces fractions to lowest terms. *)FROMSTextIOIMPORTWriteString, WriteLn, ReadChar, SkipLine;FROMSWholeIOIMPORTReadCard, WriteCard;PROCEDURESwap (VARx, y :CARDINAL); (* swaps two numbers Pre: none Post: x(post) = y (pre) and y(post) = x(pre) *)VARtemp :CARDINAL;BEGINtemp := x; x := y; y := temp;ENDSwap;PROCEDUREGcd (x, y :CARDINAL) :CARDINAL; (* This procedure computes the greatest common divisor Pre: the numbers supplied are non-zero Post: The GCD of the supplied arguments is returned *)VARresult :CARDINAL;BEGIN(* arrange the numbers in order *)IFy > xTHENSwap (x, y)END; (* compute the GCD *)REPEATresult := y; (* let the gcd be the smaller of the two *) y := xMODy; (* replace the smallest with new remainder *) x := result; (* & larger with smaller one from last step *)UNTILy = 0;RETURNresult; (* and send it back *)ENDGcd;VAR(* main program variables *) num, denom, gcd :CARDINAL; goodData, again :BOOLEAN; answer :CHAR;BEGINWriteString ("This program reduces fractions to lowest terms."); WriteLn;REPEAT(* main repeat loop *) (* get the numbers *) WriteString ("Enter the numerator ==> "); ReadCard (num); SkipLine; WriteLn; WriteString ("And now, the denominator ==> "); ReadCard (denom); SkipLine; WriteLn; (* set a flag if the numbers weren't zero *) goodData := (num # 0)AND(denom #0); (* write the result *) WriteString ("The lowest terms fraction is"); WriteLn;IFgoodDataTHENgcd := Gcd (num, denom); (* call the procedure to do the calculation *) num := numDIVgcd; (* compute new fraction *) denom := denomDIVgcd; WriteCard (num, 0); WriteLn;IFdenom # 1 (* denominators of one are not printed *)THENWriteString ("-----"); WriteLn; WriteCard (denom, 0); WriteLn;END;ELSIFnum = 0THENIFdenom = 0THENWriteString ("indeterminate; both parts are zero");ELSE(* only numerator is zero, so ok *) WriteCard (num, 0);END;ELSE(* only denominator was zero *) WriteString ("undefined because the denominator is zero")END; WriteLn; WriteString ("Do another? Y/N "); ReadChar (answer); SkipLine; again := (CAP(answer) = "Y");UNTILNOTagain;ENDReduceFraction.

Here is a run from this program module:

This program reduces fractions to lowest terms. Enter the numerator ==>51And now, the denominator ==>17The lowest terms fraction is 3 Do another? Y/NYEnter the numerator ==>91And now, the denominator ==>1001The lowest terms fraction is 1 ----- 11 Do another? Y/NYEnter the numerator ==>12And now, the denominator ==>0The lowest terms fraction is undefined because the denominator is zero Do another? Y/NYEnter the numerator ==>0And now, the denominator ==>0The lowest terms fraction is indeterminate; numerator and denominator both zero Do another? Y/NN

A wide variety of simple functions can be implemented in typical Modula-2 programs. Here is one that squares the supplied real parameter.

PROCEDURESqr (numToSquare :REAL) :REAL; (* Pre: numToSquare <= sqrt (MAX(REAL)) Post: the square of numToSquare is returned *)BEGINRETURNnumToSquare * numToSquare;ENDSqr;

Here is one based on a built-in procedure (ODD) that has not previously been mentioned:

PROCEDUREEven (num :CARDINAL) :BOOLEAN; (* Pre : none Post : returns true if num is even and false if num is odd *)BEGINRETURNNOTODD(num);END;

A little more work is required for some others:

Write a procedure to raise a real number to a real exponent.

What is desired here is the solutions to equations such as x = a^{b}. This is done as follows:

Take natural logarithms on both sides, obtaining

- ln (x) = b x ln (a)

Now, raise both sides to the power e (the base of the natural logarithms)

- x=e

This is expressed in Modula-2 by the assignment

- x := exp (b * ln (a) )

where both *exp* and *ln* must be imported from the library module *RealMath*. This produces the following code:

PROCEDUREPower (base, exponent :REAL) :REAL; (* pre: base raised to the exponent is not greater thanMAX(REAL) and base > 0 post: base raised to the exponent is returned *)BEGINRETURNexp (exponent * ln (base))ENDPower;

This function is actually provided as part of *RealMath*, but in a similar manner, it might be necessary to compute logarithms with some other base than e. (Non ISO versions may provide base ten, or common logarithms in a library). If one starts with

- x = log

and wishes to find x; one can rewrite changing to exponent notation

- b

and taking natural logarithms on both sides, yields

- x * ln (b) = ln (a) or, x = ln (a) / ln (b)

It is now fairly easy to write:

PROCEDURELogBaseB (base, number :REAL) :REAL; (* Pre : bothbaseandnumberare positive reals Post : the log of number with the base base is returned. *)BEGINRETURN(ln (number)) / (ln (base))ENDLogBaseB;

The mathematics library mentioned above also contains a procedure for computing square roots, but this computation is not difficult, and is demonstrated below:

To write a function to compute square roots of real numbers.

1. Check to see if the number provided is negative. 2. If it is, return without going further. 3. Otherwise, apply a square root algorithm. 4. Return the answer.

Basis: start with a guess at the square root; call it g. Now if g^{2} = x exactly, then g= x/g and the calculation is finished. If g^{2} > x then x/g < g so take the average of x/g and g as the next guess and try the test again. When the result is close enough, return it.

3.1 Decide how close the result must be, say epsilon < .00001. 3.2 Guess an initial value. (say, the first guess is set to half the number.) 3.3 While guess and number/guess are not within epsilon of each other Take the average of guess and number/guess for a new guess.

The code is formulated in such a way that if the precondition that requires a non-negative parameter is ignored by the client code invoking this routine, it will still be safe. The value returned will be invalid, however.

PROCEDURESqrt (num :REAL) :REAL; (* precondition: num >= 0 postcondition: if num < 0 the answer is invalid, otherwise the square root is returned. method: divide and average *)CONSTepsilon = 0.00001; (* error limit *)VARguess :REAL;BEGINIFnum <= 0.0THENRETURNnum;ELSEguess := num/2.0;END;WHILEABS(num/guess - guess ) > epsilonDOguess := 0.5 * (num/guess + guess )END;RETURNguess;ENDSqrt;

**NOTE**: The precision of the result could be improved by making the type of the variable LONGREAL and then making epsilon much smaller. Such decisions depend on the range of the two real types in the implementation being used.