Part 7. Standard and user-written functions

(c) 2017 by Barton Paul Levenson



Part 7. Standard and user-written functions

There are lots of intrinsic functions in Fortran--and you don't have to include special libraries to use them, as in C or Java. Some of the strictly mathematical ones are:


function(s)meaning
abs
absolute value
acos, asin, atan
arc trigonometric functions
atan2
arc tangent with quadrant specified
cos,  sin,  tan
trigonometric functions
cosh, sinh, tanh
hyperbolic functions
exp
ex
log,  log10
natural log, common log
sqrt
square root

You can use a function in pretty much any mathematical assignment statement--or a character assignment statement; there are character-based functions as well. For example:


    answer = sqrt(2d0)		! Find the square root of 2.


The choice of log for ln is unfortunate, but at least Fortran provides a log10 function.

But let's suppose we want a log2 function, perhaps for statistical or probability work. Users can write their own functions. Here is one to compute the base-two logarithm of a number:


! lg computes the base-two log of x.
real(d) function lg(x)
    real(d), intent(in) :: x

    lg = log(x) / log(2d0)
end function lg


Note the standard format of a user-written function--return data type, the keyword function, a name, argument(s) in parentheses. If there are no arguments, you still need an empty pair of parentheses in both the function header and the function call.

If arguments are present, each must be declared as if it were a local variable. The intent clause, which can have the attribute in, out, or inout, is not required, but helps the compiler.

Then an assignment statement is made, using the name of the function as a variable. The function ends with the keywords end function and, again, the function name.

With this, we can write lg(x) wherever we want a base-two log. It acts like any of the built-in functions.

We can make the function more detailed than a single line of code. For example, we can check if the argument is valid:


! lg computes the base-two log of x.
real(d) function lg(x)
    real(d), intent(in) :: x

    if (x <= 0d0) then
        write (*, 10) x
10      format(/'Domain error in log2 function.'/                   &
        &       'Argument ', es12.4, ' is less than or equal to 0.'/)

        lg = 0d0
    else
        lg = log(x) / log(2d0)
    end if
end function lg


This is a lot handier than including several lines of error-checking code whenever a base-two log is taken.

In fact, any repetitive segment of code can be placed in a program module by itself. If nothing has to be returned, the module is not a function but a subroutine, with the following syntax:


    subroutine name(argument(s))
        declare argument(s), if any
        declare local variable(s), if any
        statement(s)
    end subroutine name


A function can also have local variables.

A subroutine is called not by putting it in an assignment statement, but by using the call keyword. Functions and subroutines are sandwiched into the main program following the keyword contains.

Here is a program with a subroutine:


! Program with a user-written subroutine.
program trig
    integer, parameter :: d = selected_real_kind(p = 15)

    character(20) :: reply		! The reply itself.
    real(d)       :: value		! Numeric value of reply.

    write(*, *) 'This program finds trig functions of values you enter.'
    write(*, *) 'Enter angles in degrees, or BYE or QUIT to stop.'
    write(*, *)				! This prints a blank line.

    do
        write (*, 10)
10      format(/, 'Value or stop code:  =>', $)
        read  (*, *) reply
        call upcase@(reply)		! Capitalize reply.

        if (reply == 'BYE' .or. reply == 'QUIT') then
            write (*, *) 'Thanks.  Bye now.'
            exit			! Quit program.
        else
            read(reply, *) value	! Convert string to number.
            call trigStuff(value)	! Call the subroutine.
        end if
    end do
contains



! trigStuff applies trig functions to the user-entered value.
subroutine trigStuff(x)
    real(d), intent(in) :: x
    real(d), parameter  :: pi = 3.141592653589793238d0
    real(d), parameter  :: degToRad = pi / 180d0
    real(d)  :: radians
    
    radians = x * degToRad			! Convert to radians.

    write (*, 10) x, sin(radians), x, cos(radians), x, tan(radians)
10  format(/'sine    ', f8.3, ' degrees = ', f18.15 &
    &       'cosine  ', f8.3, ' degrees = ', f18.15 &
    &       'tangent ', f8.3, ' degrees = ', f18.15)
end subroutine trigStuff

end program trig


Note the following points in the main program:

1. The upcase@ function is peculiar to Salford Fortran. I hope other Fortran compilers out there have similar routines, but I can't guarantee it. In the worst case, you might have to write your own code to do this. Make it a function so you can use it again in other programs.

Converting to upper-case enables me to check for only two possible quit commands (reply == "BYE" .or. reply == "QUIT"). If I left out the conversion, I would also have to check for "bye," "Bye," "quit," "Quit," and for that matter, "QuIt" and similar variations.

2. The line


     read(reply, *) value			! Convert string to number.


is an "internal read." It converts the string in the reply variable to the double-precision number in the value variable, if, at this point, the program has recognized reply as a number and not "bye" or "quit".

I needed to read in a string value, rather than take a number directly, to allow the BYE and QUIT keywords. Checking for a sentinel value like 0 or a negative number would have ruled out taking trig functions of 0 or negative numbers, which are perfectly valid angle measurements.

3. The call statement invokes the trigStuff subroutine, passing the appropriate argument list (in this case, just one variable).

In trigStuff:

1. Local variables in a subroutine or function are visible to the program only while executing that subroutine or function.

2. I do a calculation in the middle of declaring a constant. Fortran allows this as long as the math is kept simple and no functions are called.

3. Trig functions in Fortran require an argument in radians, not degrees, grads, or cycles.

4. A calculation or function call can go in a write statement wherever a variable or constant can.

5. The code in this subroutine can be written out on full lines, leaving plenty of space for comments. We could have sandwiched all this into the else clause in the program's main loop, but with a reasonable indentation convention, we wouldn't have had much room for documentation. Subroutines and functions make programs neater and easier to read.

If no contains statement is used in the main program, functions and subroutines can be written after the end program statement. But in that case, a function, but not a subroutine, has to be declared in the main program as if it were a variable, or the compiler will think it's a variable. I can't begin to fathom the reasoning behind that. Suffice to say if you have any user-written functions, but no contains statement, declare them in the main program with the appropriate data type.





Page created:05/05/2017
Last modified:  05/05/2017
Author:BPL