11. 부 프로그램(Subprograms)

어떤 프로그램이 수백 줄로 길어지면 따라가기 어렵게 된다. Fortran codes 실제의 공학적 문제를 해결하기 위한 Fortran 코드는 수만 줄 씩 되기도 한다. e only way to handle such 이런 엄청난 코드를 다루는 유리한 방법은 modular approach을 써서 프로그램을 부프로그램subprograms이라고 하는 작은 단위 여러 개로 나누는 것이다.

부 프로그램이란 확실하게 정의된 작은 문제를 해결하는 (작은) 코드 조각이다. 큰 프로그램에서는 다른 자료를 갖고 같은 문제를 풀어야 하는 경우가 종종 있다. 이러 작업은 같은 코드를 반복하는 대신에 부프로그램을 사용하여 해결하여야 한다. 같은 부프로그램을 입력 자료만 다르게 하여 여러 차례 사용할 수 있다.

Fortran에는 두 가지 형태의 부프로그램 - 함수 functions 와 서브루틴 subroutines -이 있다.

함수 (Functions)

Fortran 함수는 수학의 함수와 매우 비슷하다. 둘 다 일단의 입력 변수 (parameters)를 받아들여 어떤 형태의 값을 준다. 앞에서 사용자 정의 user defined 부프로그램에 대하여 살펴보았다. Fortran 77 역시 내장 built-in 함수가 있다.

다음의 간단한 예는 함수를 사용하는 방법이다.

      x = cos(pi/3.0)
여기서 cos은 cosine 함수이고 따라서 (pi가 올바르게 정의되어 있다면 (Fortran 77에는 내장된 상수는 없다.)) x는 0.5가 될 것이다. Fortran 77에는 내장 함수가 여러 가지 있다. 가장 많이 사용하는 것으로는:
      abs     absolute value
      min     minimum value
      max     maximum value
      sqrt    square root
      sin     sine
      cos     cosine
      tan     tangent
      atan    arctangent
      exp     exponential (natural)
      log     logarithm (natural)
보통 함수는 형태 type가 있다. 위에서 언급한 대부분의 내장 함수는 일반적generic이다. 따라서 위 예에서 pi와 x 는 real이거나 double precision이다. compiler는 형태를 조사하여 알맞는 (real 또는 double precision) cos을 사용할 것이다. 불행하게도, Fortran은 다양한 polymorphic 기능의 언어가 아니라 일반적으로는 사용자가 조심하여 변수와 함수의 형태를 맞추어야 한다!

이제 사용자가 만든 함수를 살펴보자. 다음 문제를 풀어보자: 지질학자가 Bay Area의 강수량을 조사하여 관계 수식 r(m,t) 를 구하였다. 이 식에서 r 은 비의 양, m 은 달이고, t 는 위치에 따라 다른 scalar parameter이다. r에 관한 식과 t 값을 사용하여 연간 강수량을 계산한다.

이 문제를 푸는 분명한 방법은 모든 달에 대하여 계산하는 loop를 만들어 더함으로써 r의 값을 구하는 것이다. r의 값을 구하는 것은 독립적인 작은 문제이므로 이를 함수로 하는 것이 편리하다. 다음 주 프로그램 (main program)을 사용할 수 있다:

      program rain
      real r, t, sum
      integer m
 
      read (*,*) t
      sum = 0.0
      do 10 m = 1, 12
         sum = sum + r(m, t)
  10  continue
      write (*,*) 'Annual rainfall is ', sum, 'inches'

      stop
      end
덧붙여 함수 r은 함수로 정의하여야 한다. 지질학자가 얻은 공식은 다음과 같다.
      r(m,t) = t/10 * (m**2 + 14*m + 46) if this is positive
      r(m,t) = 0                         otherwise        
이를 Fortran function로 나타내면 다음과 같다.
      real function r(m,t)
      integer m
      real t

      r = 0.1*t * (m**2 + 14*m + 46)
      if (r .LT. 0) r = 0.0

      return
      end
함수의 구조가 주 프로그램과 닮았다는 것을 알 수 있다. 그 차이는:

요약하면 Fortran 77 함수의 일반적 syntax는 다음과 같다.

      type function name (list-of-variables)
      declarations
      statements
      return
      end

함수는 부르는 프로그램에서 정확한 형태로 선언되어야 한다. 그리고 그 함수 이름과 괄호 안에 매개 변수를 나열하여 부른다.

서브루틴 (Subroutines)

Fortran의 function은 일반적인 함수와 마찬가지로 오직 한가지 결과만을 갖고 돌아오는 것이 원칙이다. 때때로는 두 개나 그 이상의 변수 (때때로는 결과 변수 없이)값을 갖고 돌아 오게하고 싶을 때가 있다. 이런 경우에는 서브루틴 subroutine 구조를 사용하여야 한다. syntax는 다음과 같다.
      subroutine name (list-of-arguments)
      declarations
      statements
      return
      end
서브루틴은 type이 없고 따라서 이 서브루틴을 부르는 프로그램에서 종류 type 를 선언해줄 필요가 없다.

다음은 아주 간단한 서브루틴의 예이다. 이 서브루틴에서는 두 정수 변수의 값을 서로 바꾸는 것이다.

      subroutine iswap (a, b)
      integer a, b
c Local variables
      integer tmp

      tmp = a
      a = b
      b = tmp

      return
      end
여기에는 변수 선언이 두 block으로 되어있음에 주의한다. 우선 입출력 매개 변수, 즉 부르는 프로그램 caller 과 불리어지는 프로그램 callee 양쪽에 공통인 변수를 선언한다. 그리고 나서 국부 변수 local variables, 즉 이 부프로그램 안에서만 사용하는 변수를 선언한다. 서로 다른 부 프로그램에서 같은 변수 이름을 사용할 수 있고 compiler는 이를 이름이 같지만 다른 변수로 취급한다.

Call-by-reference

Fortran 77은 소위 call-by-reference 방식을 취한다. 이는 함수/서브루틴 매개 변수의 값만을 넘겨주는 (call-by-value) 대신 변수의 memory 주소 (pointer)를 넘겨주는 것을 의미한다. 다음 간단한 예가 그 차이를 설명한다.
      program callex
      integer m, n
c
      m = 1
      n = 2 

      call iswap(m, n)
      write(*,*) m, n

      stop
      end
이 프로그램의 결과는 예상할 수 있는대로 "2 1"이다. 그러나 Fortran 77에서 call-by-value를 사용한다면 결과는 "1 2"로 변수 m과 n이 서로 바뀌지 않는다. 이는 m과 n의 값만이 서브루틴 iswap에 전달된다면 m과 n이 서브루틴 내부에서 서로 비뀐다하더라도 새 값은 주 프로그램에 전달되지 않을 것이기 때문이다.

위 예제에서 call-by-reference가 정확하게 우리가 원하는 바이다. 하지만 이 때문에 Fortran code를 쓸 때 원하지 않던 부작용 side effects이 일어나지 않도록 주의하여야 한다. 한 예로, For example, sometimes it is tempting to use an input parameter 부 프로그램 안에서 입력 매개 변수를 국부 변수로 사용하면 편할 때가 잇는데 이 경우 그 값이 바뀌어 버린다. 그 결과 기대하지 않던 값이 이 부프로그램을 부른 프로그램으로 퍼져나가게 되기에 결코 사용하여서는 안 된다.

뒷 장에서 배열(array)을 매개 변수로 넘겨 주는 문제에서 다시 살펴 볼 것이다.


Exercises

Exercise A
정수 n을 받아들여서 n! (n factorial)을 돌려 보내는 function fac을 작성한다. 작성한 프로그램을 다음 주프로그램을 사용하여 검사하라.
      program tstfac
c
c Exercise A, section 11.
c Main program to test factorial function.
c
      integer n, fac

  10  continue
         write(*,*) 'Give n: '
         read (*,*) n
         if (n.gt.0) then
            write(*,*) n, ' factorial is', fac(n)
            goto 10
         endif
c     End of loop

      stop
      end
n = 5, 10, 20일 때의 결과를 살펴본다. 또 이 프로그램을 정지시키는 방법을 알아본다. (Hint: You have to use a loop in your function since Fortran 77에서는 자신이 자신을 부르는 재귀 recursion이 불가능하므로 fucntion에서 loop를 사용하여야만 한다.)

Exercise B
세 실수 a, b, c를 입력으로 하여 이차방정식 ax**2 + bx + c = 0의 두 실근을 구하는 서브루틴 qsolve를 작성하라. 근이 허수이면 다음과 같은 오류 정보 error message를 출력하게 한다.
      write(*,*) 'Warning: Complex roots!'
처음에는
      a=2.0, b=9.0, c=4.0
으로 다음에는
      a=2.0, b=0.0, c=4.0
의 숫자로 위에서 작성한 서브루틴을 검사할 수 있는 주 프로그램을 작성하라.


[Fortran Tutorial]
충남대학교 물리학과 오 병성