Fortran: Deprecated Features

As the language has progressed many of the features used in previous versions are now deprecated either because there are newer features in the language which can perform the same task in a better way, or because the reason they were introduced in the first place no longer applies. Here we outline some of these features and suggest better alternatives.

Arithmetic IF

The Arithmetic IF statement is a three-way branch depending on whether the arithmetic expression is less than, equal to or greater than zero. eg.

IF (X) 10, 50, 60

This means that the arithmetic expression X is evaluated and control would branch to line 10 if X < 0, 50 if X = 0 or 60 if X > 0. Clearly this can be rewritten as a Block IF instead thus:

IF (X < 0) THEN
   ...
ELSE IF (X == 0) THEN
   ...
ELSE
   ...
END IF

Non-integer control variables

It is possible in Fortran 90 to use variables of types other than INTEGER as the control variable (the iterator) in a DO-loop. However, the problem comes when on the last iteration. Given the inability to store real numbers precisely, if we are to loop from 1.0 to 10.0, say, then on what should be the last iteration, the control variable should have value 10.0, but it might actually be 10.000001 which means that the loop would not be executed that final time.

Such techniques are mostly used when stepping over non-integral values as well; eg.

DO A = 1.0, 5.0, 0.5
  PRINT *, A
END DO

However, this can easily be changed to use integral steps and then scale down within the loop itself. eg.

DO I = 2, 10
  A = I / 2.0
  PRINT *, A
END DO

Note that in Fortran 95 use of non-integer control variables is no longer permitted.

Non-ENDDO Loop Termination

It is considered good practice these days always to end each DO loop with an END DO statement. This helps to make it explicitly clear where the loop ends and removes the need to rely on line numbers. As a consequence of this, the possibility of shared loop termination is avoided as well. Consider this set of nested loops.

DO 60 I = 1, 10
  DO 60 J = 50, 55
    60 K(I,J) = 250 - I ** 2 * J

These could be much more clearly written as

DO I = 1, 10
  DO J = 50, 55
    K(I,J) = 250 - I ** 2 * J
  END DO
END DO

Alternate RETURN

An alternate RETURN is used to specify different return points in the calling routine when exiting from a called subroutine. This is about as non-structured as programming gets. Consider this section of program and subroutine.

(In the program)
100 X = X + Y
...
CALL MYSUB (X, Y, *100, *150)
...
150 PRINT *, X, Y
(In the subroutine)
SUBROUTINE MYSUB (A, B, *, *)
...
IF (A == B) RETURN 1
RETURN 2

In this case, when we reach the final IF statement in the subroutine, if A and B have the same value, the subroutine returns to the calling program and execution continues not from where the CALL statement is, but rather at line 100 since this is the first of the asterisked arguments. Otherwise, execution returns to line 150, being the second asterisked argument.

Tracing the path of execution through the code is much better if a flag is set in the subroutine and tested after execution returns to the calling routine.

(In the program)
CALL MYSUB (X, Y, IFLAG)
IF (IFLAG == 1) THEN
  X = X + Y
ELSE 
  PRINT *, X, Y
END IF
(In the subroutine)
SUBROUTINE MYSUB (A, B, IRET)
...
IF (A == B) THEN
  IRET = 1
ELSE
  IRET = 0
END IF

PAUSE

Back in the days when the best one could hope for was a VT52, the PAUSE statement was quite useful to temporarily pause the program while the user read the output. The downside is that if such a piece of code is sent off to batch it will dilligently wait until someone presses a key (which is unlikely to happen).

Today we have fancy windowing systems and a plethora of pagers from which to choose, so it is rarely needed. However it can be directly replaced with a READ (*, *) if required. The PAUSE statement itself has been deleted from the Fortran 95 standard.

Assigned GOTO

As if GOTO statements weren't enough trouble on their own, with ASSIGN they can be made to have dynamic targets.

ASSIGN 100 TO IGO
GOTO IGO

In the above, the label 100 is assigned to the dynamic label IGO, so when we GOTO IGO, execution jumps to line 100. However, note that the ASSIGN statement could be inside an IF block or a loop or both and so use of this feature does not help when it comes to tracing execution through the code. ASSIGN has been deleted from the Fortran 95 standard.

Assigned FORMAT

Similarly, we can assign to a FORMAT statement in the case where the format will change depending upon some condition in the program. eg.

ASSIGN 100 TO IFOR
IF (A >= 1000.0 .OR. B >= 1000.0) ASSIGN 200 TO IFOR
WRITE (*, IFOR) A, B, C
100 FORMAT (1X, 3F8.4)
200 FORMAT (1X, 3E8.1)

In the above code, it is relatively clear why this is being done, but if the relevant FORMAT statements are far away in the code it is much more difficult to see. A better approach is to use character variables to store the precise formats as in this example.

CFOR = '(1X, 3F8.4)'
IF (A >= 1000.0 .OR. B >= 1000.0) CFOR = '(1X, 3E8.1)'
WRITE (*, CFOR) A, B, C

H Edit Descriptor

H is for Hollerith, or CHARACTER data. It was used when formatting output to allow literal strings to be present in the format statement. eg.

WRITE (*, '(1X, I3, 8H * 12 = , I3)') 2, 24

This would display the line "   2 * 12 =  24". Today the same functionality can be achieved with quotation marks. These have the added advantage that one cannot get the count wrong.

WRITE (*, '(1X, I3, " * 12 = ", I3)') 2, 24

The H edit descriptor has been deleted from the Fortran 95 standard.

DOUBLE PRECISION

The old DOUBLE PRECISION type gives a floating point number with double the precision of the REAL type. However, since the precision of the REAL type is platform-dependent using DOUBLE PRECISION gives you twice as much precision as something undetermined - not useful when writing portable code. Now we can use SELECTED_REAL_KIND() to request a kind type which will provide precisely the required precision instead.

Implicit typing

Implicit typing seemed like a Really Smart Move back in the late fifties when Fortran was spelled in caps and 4K was a massive amount of memory. Today, however, it is an anachronism. It should be the objective of the structured Fortran programmer to declare (and initialise) every variable, parameter and array used in each block, and with that in mind IMPLICIT NONE should be present in every block as the first statement (after any USE statements).

Common blocks and block data

Common blocks have been widely used in Fortran for many years - mostly due to the lack of user-defined types and the insufficient mechanisms for argument checking. A common block is a named block of global data and is often inserted in code by use of INCLUDE statements.

PROGRAM commontest
  IMPLICIT NONE
  REAL    ::  INDATA
  COMMON  /SUNRISE/INDATA(10)
  PRINT *, INDATA(1)
END PROGRAM commontest

BLOCK DATA
  IMPLICIT NONE
  REAL    ::  INPUT
  COMMON /SUNRISE/INPUT(10)
  DATA INPUT /2.2,1.8,3.6,0.5,2.4,2.1,0.9,1.7,1.6,3.1/
END BLOCK DATA

If global data is to be used at all, it is much better to do so by storing it in a module and then using that module in the blocks which require access to the data. In this way a single name is used for each element of global data throughout the program and compile-time checks can catch many problems which would otherwise go undetected.

EQUIVALENCE

In the contemporary textbook Programming in Standard FORTRAN 77 by Balfour and Marwick, the authors have this to say:

An EQUIVALENCE statement is used to specify that two or more entities in the same program unit are to share storage units, a facility of dubious value.

There are seldom good reasons for using EQUIVALENCE statements, and devious bugs may be introduced by using EQUIVALENCE statements in ways which are not completely straightforward.

The EQUIVALENCE statement is a method of assigning different names to the same storage entities in the manner of aliases. This might not seem on the surface to be very useful, however the power (and therefore also the danger) of this facility is down to the lack of restrictions on the size, shape and type of the variables and arrays being aliased. The only such restriction is that character and non-character types cannot be mixed. The following is therefore possible.

REAL  :: A(100), B(10,50), C
INTEGER :: I(2000), J(2000), K(10,10,10)
EQUIVALENCE (A,B,C,I),(J,K)

In this example, the labels A, B, C and I point to the same storage space and the labels J and K each point to another storage space. The sizes and the types can all be different and so it becomes very easy to corrupt data via type mismatches or dereferencing beyond bounds. This is perhaps the worst feature still present in the Fortran language as it is far too easy to introduce insidious bugs in this way. It can be replaced in modern code by appropriate use of pointers and the RESHAPE and TRANSFER intrinsic functions.

ENTRY

Within a function or subroutine an ENTRY statement may appear which allows an alternative point of entry to the procedure from the calling routine.

SUBROUTINE MYSUM (A, B, C)
  IMPLICIT NONE
  REAL, INTENT (IN)  :: B, C, X, Y, Z
  REAL, INTENT (OUT) :: A
  A = B + C
  RETURN
  ENTRY MYSUM2 (A, X, Y, Z)
  A = X + Y + Z
  RETURN
END SUBROUTINE MYSUM

This subroutine has two points of entry, the standard one at the top (MYSUM) and the one at the ENTRY statement (MYSUM2). It can be called by either name in the calling routine, but clearly the arguments differ. This can be very confusing and is unnecessary. With modern conveniences such as optional arguments, keyword arguments and pointers, the need for this construct has been entirely superceded.

Statement Functions

A statement function is a function declaration and description on a single line which may only be executed by the enclosing subprogram block. A simple function and its use might be:

REAL :: X, CUBE
CUBE (X) = X ** 3
PRINT *, CUBE (4.0)

The notation employed is somewhat confusing here - CUBE might be a function, or it could just as easily be an array. Writing out the function as a separate block removes any ambiguity and makes the declaration easier to find by searching in the file. Use of the CONTAINS statement allows the function's scope to be restricted to that of the enclosing block.

PRINT *, CUBE (4.0)
CONTAINS
  REAL FUNCTION CUBE (X)
    IMPLICIT NONE
    REAL, INTENT (IN) :: X
    CUBE = X ** 3
  END FUNCTION CUBE

Computed GOTO

The computed GOTO takes an integer expression and branches to the statement numbered by the nth element of a supplied list where n is the result of the integer expression. Hence if the code reads:

GOTO (1000, 44, 196) I - 17

then the execution will branch to line 1000 if I has value 18, to line 44 if I has value 19 and to line 196 if I has value 20. If I is outside this range, then no branching is done. Instead of this method which can be unclear and is always difficult to maintain because of hard-coded line numbers, we can make the most of the CASE construct.

SELECT CASE (I - 17)
  CASE (1)
    ...
  CASE (2)
    ...
  CASE (3)
    ...
  CASE DEFAULT
    ...
END SELECT