Skip to content

Fortran 치트시트

과학 및 수치 컴퓨팅을 위한 선구자 언어.

01

기본 & 프로그램 구조

프로그램 구조 & Hello World

모든 Fortran 프로그램은 'program NAME'으로 시작하여 'end program NAME'으로 끝납니다. 'implicit none'은 현대 Fortran에서 필수 — 모든 변수의 명시적 선언을 강제(없으면 Fortran은 i-n으로 시작하는 변수는 정수, 다른 것은 실수로 암묵적 타이핑을 사용, 이는 버그의 주요 원인). 'contains' 블록은 실행 코드를 내부 프로시저(프로그램 내에 정의된 서브루틴/함수)와 분리. 주석은 '!'로 시작. 자유 소스 형식(Fortran 90+)은 .f90 확장자 사용; 열은 중요하지 않음. gfortran/ifort로 컴파일.

fortran
program hello
  ! A complete Fortran program structure
  implicit none
  ! declarations go here
  integer :: status = 0

  print *, "Hello, World!"   ! list-directed output to stdout

  ! executable statements
  call do_work(status)

  print *, "Exit status: ", status
contains
  subroutine do_work(st)
    integer, intent(out) :: st
    st = 0
    print *, "Working..."
  end subroutine do_work
end program hello

! Compile: gfortran hello.f90 -o hello
! Run:     ./hello

변수 & 내장 타입

Fortran에는 5개의 내장 타입: integer, real, complex, character, logical. 'kind'는 정밀도/크기 선택 — 64비트에는 kind=8 사용(또는 이식성을 위해 selected_real_kind/iso_fortran_env 사용). 실수 리터럴은 kind 접미사 필요: 3.14_8(3.14만이 아님). double precision은 real(kind=8)의 레거시 구문. 복소수 리터럴은 (real, imag) 형식 사용. 논리값은 .true. / .false.(점 포함). 문자열은 len=:와 allocatable(지연 길이, Fortran 2003+)으로 선언하지 않는 한 고정 'len'을 가짐. 조용한 정밀도 손실을 피하기 위해 항상 일치하는 kind 접미사로 초기화.

fortran
program variables
  implicit none
  ! Integer types
  integer :: count = 0
  integer(kind=8) :: big = 9223372036854775807_8   ! 64-bit
  ! Real types
  real :: x = 3.14              ! default (often 32-bit)
  real(kind=8) :: y = 2.718281828459045_8   ! double precision
  double precision :: z = 1.0d0
  ! Complex
  complex :: c = (1.0, 2.0)    ! 1 + 2i
  complex(kind=8) :: cw = (1.0_8, 2.0_8)
  ! Character
  character(len=20) :: name = "Alice"
  character(len=:), allocatable :: flexible   ! deferred length
  ! Logical
  logical :: flag = .true.
  ! Print all
  print *, count, big
  print *, x, y, z
  print *, c, cw
  print *, name, flag
end program variables

상수 & 매개변수

상수는 'parameter' 속성을 사용하고 선언 시 초기화되어야 함. 수정 불가 — 컴파일러가 최적화하고 인라인 가능. 관례상 SCREAMING_SNAKE_CASE 사용. character(*)은 '초기화에서 길이 가져오기' 의미(문자열 상수에 편리). 매개변수는 배열 크기, 물리 상수, 열거형 같은 정수 코드에 일반적으로 사용. Fortran 2003+은 적절한 ENUM 타입도 있지만 매개변수 정수가 관용적 선택. 매개변수는 배열 차원 선언과 다른 상수 식 컨텍스트에서 사용 가능.

fortran
program constants
  implicit none
  ! Named constants via 'parameter' attribute
  integer, parameter :: MAX_SIZE = 100
  real, parameter :: PI = 3.14159265
  real, parameter :: E = 2.718281828
  character(*), parameter :: APP_NAME = "MyApp"
  ! Using parameters
  real :: arr(MAX_SIZE)
  arr = 0.0
  print *, APP_NAME, " size=", MAX_SIZE
  print *, "Circumference: ", 2.0 * PI * 5.0

  ! Enum-like via parameter
  integer, parameter :: SUNDAY = 1, MONDAY = 2, TUESDAY = 3
  integer :: day = MONDAY
  print *, "Day code: ", day
end program constants

연산자 & 표현식

Fortran 연산자: 산술(+ - * / **), **는 거듭제곱(Fortran 고유). 정수 나눗셈은 0으로 향해 잘림 — 진정한 나눗셈에는 real(a)/b 사용. 두 가지 관계 구문: 현대(< > == /= <= >=)와 레거시(.lt. .gt. .eq. .ne. .le. .ge.). 논리: .and. .or. .not. .eqv.(동등) .neqv.(배타적 또). 문자열 연결은 // 사용; trim()은 후행 공백 제거(Fortran은 고정 길이 문자열을 공백으로 채움). mod vs modulo: mod는 잘린 나눗셈 부호 따르고, modulo는 내림 나눗셈 따름 — 음수 피연산자에 대해 다름.

fortran
program operators
  implicit none
  integer :: a = 17, b = 5
  real :: x = 2.0
  ! Arithmetic
  print *, a + b, a - b, a * b    ! 22 12 85
  print *, a / b                   ! 3 (integer division!)
  print *, real(a) / b             ! 3.4 (cast to real)
  print *, a ** 2                  ! 289 (exponentiation)
  print *, mod(a, b)               ! 2 (modulo)
  print *, modulo(a, b)            ! 2 (differs for negatives)
  ! Relational (both forms work)
  print *, a > b, a < b            ! T F
  print *, a .gt. b, a .lt. b      ! T F (old form)
  print *, a == b, a /= b          ! F T
  ! Logical
  print *, (a > 0) .and. (b > 0)   ! T
  print *, (a > 0) .or. (b < 0)    ! T
  print *, .not. (a > 0)           ! F
  print *, (a > 0) .eqv. (b > 0)   ! T (equivalence)
  ! String concatenation
  character(10) :: s1 = "Hello", s2 = "World"
  print *, trim(s1) // " " // trim(s2)   ! Hello World
end program operators

내장 함수 & 수학

Fortran은 풍부한 내장(intrinsic) 함수 세트 보유. 수학: abs, sqrt, exp, log(자연), log10, sin/cos/tan/asin/acos/atan/atan2, sinh/cosh/tanh. 반올림: int(잘림), nint(가장 가까운), floor, ceiling. 변환: real(), int(), cmplx(). 조회: size, shape, huge(최대값), tiny(최소 양수), kind. 모든 삼각 함수는 라디안 사용. atan2(y, x)는 올바른 사분면의 각도 반환(atan과 달리). 범위 제한 확인에는 huge/tiny 사용. 내장 함수는 기본적 — 배열에서 자동으로 요소별 작동.

fortran
program intrinsics
  implicit none
  real :: x = -3.7, y = 2.5
  ! Math functions
  print *, abs(x)         ! 3.7
  print *, sqrt(2.0)      ! 1.414...
  print *, exp(1.0)       ! 2.718... (e^x)
  print *, log(2.0)       ! 0.693... (natural log)
  print *, log10(1000.0)  ! 3.0
  print *, sin(3.14159/2) ! 1.0
  print *, cos(0.0)       ! 1.0
  print *, atan2(1.0,1.0) ! 0.785... (pi/4)
  ! Rounding
  print *, int(x)         ! -3 (truncate toward zero)
  print *, nint(x)        ! -4 (nearest integer)
  print *, floor(x)       ! -4 (toward -inf)
  print *, ceiling(x)     ! -3 (toward +inf)
  print *, abs(x), max(x, y), min(x, y)   ! 3.7 2.5 -3.7
  ! Type conversion
  print *, real(5)        ! 5.0
  print *, int(3.9)       ! 3
  ! Inquiry
  real :: arr(10)
  print *, size(arr)      ! 10
  print *, huge(1)        ! 2147483647
  print *, tiny(1.0)      ! smallest positive real
end program intrinsics
02

제어 흐름

If...Then...Else

블록 IF: 'if (cond) then ... else if (cond) then ... else ... end if'. 각 분기는 'then' 필요(마지막 else 제외). 논리 IF는 한 줄: 'if (cond) statement'('then'/'end if' 없음). 조건은 관계 연산자(< > == /= <= >= 또는 .lt. .gt. .eq. .ne. .le. .ge.)를 .and. .or. .not.과 결합. 'stop'은 프로그램 종료(선택적으로 메시지/코드와 함께). 산술 IF(if (x) label1, label2, label3)는 Fortran 2018에서 삭제됨 — 사용 금지. 선언되지 않은 변수를 잡기 위해 항상 'implicit none' 사용.

fortran
program if_demo
  implicit none
  integer :: score = 85
  character(1) :: grade

  ! Multi-branch if/else if/else
  if (score >= 90) then
    grade = 'A'
  else if (score >= 80) then
    grade = 'B'
  else if (score >= 70) then
    grade = 'C'
  else if (score >= 60) then
    grade = 'D'
  else
    grade = 'F'
  end if

  print *, "Score ", score, " -> Grade ", grade

  ! Logical if (single statement, no 'then')
  if (score < 0 .or. score > 100) stop "Invalid score"

  ! Arithmetic if (OBSOLETE - avoid)
  ! if (x) 10, 20, 30   ! jump to label based on sign
end program if_demo

Select Case (Switch)

select case는 Fortran의 switch 문. 케이스는 단일 값(case (3)), 목록(case (1, 3, 5)), 또는 범위(case (4:5)는 4에서 5 포함)일 수 있음. case default는 폴백. C와 달리 fall-through 없음 — 각 분기는 독립적이고 하나만 실행. integer, character, logical 타입에서 작동(real은 안 됨). 문자 범위는 ASCII 순서 사용('A':'Z'). 부동소수점 비교에는 if/else 사용. integer/char 디스패치에 긴 if/else if 체인보다 select case가 더 효율적(컴파일러가 점프 테이블 사용 가능).

fortran
program case_demo
  implicit none
  integer :: day = 3
  character(1) :: ch = 'A'
  character(10) :: day_name

  ! Integer select case
  select case (day)
  case (1)
    day_name = "Monday"
  case (2)
    day_name = "Tuesday"
  case (3)
    day_name = "Wednesday"
  case (4:5)
    day_name = "Thu/Fri"
  case (6:7)
    day_name = "Weekend"
  case default
    day_name = "Invalid"
  end select
  print *, day_name

  ! Character select case (case-insensitive via pre-upper)
  select case (ch)
  case ('A':'Z')
    print *, "Uppercase letter"
  case ('a':'z')
    print *, "Lowercase letter"
  case ('0':'9')
    print *, "Digit"
  case default
    print *, "Other"
  end select

  ! Logical select case
  select case (day > 5)
  case (.true.)
    print *, "Weekend!"
  case (.false.)
    print *, "Weekday"
  end select
end program case_demo

Do 루프 (카운트)

카운트 DO 루프: 'do var = start, end, step'(step 기본값 1). 루프는 var <= end(양수 step) 또는 var >= end(음수 step)인 동안 실행. var은 각 반복 후 증가. implied-do 구문 [(expr, var=start,end)]은 배열 초기화와 I/O 목록에 강력. 명명된 루프(outer: do ... end do outer)는 특정 중첩 수준으로 cycle/exit 타겟팅 가능. 루프 변수는 자동으로 정의; Fortran에서는 루프 후 최종 값을 유지. 루프 본체 내에서 루프 변수 수정 피하세요.

fortran
program do_loops
  implicit none
  integer :: i, j, total

  ! Basic counted loop: do var = start, end [, step]
  do i = 1, 5
    print *, i          ! 1 2 3 4 5
  end do

  ! With step
  do i = 10, 1, -1      ! countdown
    print *, i
  end do

  do i = 0, 100, 25     ! 0 25 50 75 100
    print *, i
  end do

  ! Implied-do (inline, for array init / I/O)
  integer :: arr(5) = [(i**2, i=1,5)]   ! 1 4 9 16 25
  print *, arr
  print *, (i, i=1,3)                   ! 1 2 3

  ! Nested loops with labels (for cycle/exit targeting)
  total = 0
  outer: do i = 1, 3
    inner: do j = 1, 3
      total = total + i*j
    end do inner
  end do outer
  print *, "Total: ", total
end program do_loops

Do While & 무한 루프

do while (cond) ... end do는 사전 테스트 루프(각 반복 전 조건 확인; 0번 실행 가능). 사후 테스트 동작에는 do ... if (cond) exit ... end do 사용. 단독 'do ... end do'는 무한 루프 — exit 문이 있어야 함(그렇지 않으면 무한). 'exit'는 가장 안쪽 루프(또는 명명된 루프)를 떠남. 명명된 루프(factorial_loop:)는 exit가 외부 루프 타겟팅 가능. 반복 횟수를 알 수 없고 조건에 의존할 때 do while 사용; 횟수를 미리 알 때 카운트 do 사용.

fortran
program while_demo
  implicit none
  integer :: n, count
  real :: x, sum

  ! do while: pre-test loop
  n = 1024
  count = 0
  do while (n > 1)
    n = n / 2
    count = count + 1
  end do
  print *, "log2(1024) = ", count   ! 10

  ! Infinite loop with exit
  sum = 0.0
  do
    read(*, *) x
    if (x < 0) exit          ! leave loop
    sum = sum + x
  end do
  print *, "Sum: ", sum

  ! do ... end do with conditional exit
  n = 1
  factorial_loop: do
    if (n > 10) exit factorial_loop
    print *, n, factorial(n)
    n = n + 1
  end do factorial_loop

contains
  recursive function factorial(n) result(f)
    integer, intent(in) :: n
    integer :: f
    if (n <= 1) then
      f = 1
    else
      f = n * factorial(n-1)
    end if
  end function factorial
end program while_demo

Cycle, Exit & 루프 제어

cycle은 현재 반복의 나머지를 건너뛰고 다음으로 점프(C/Python의 'continue'처럼). exit는 루프를 완전히 빠져나감('break'처럼). 둘 다 기본적으로 가장 안쪽 루프 타겟팅, 하지만 명명된 루프(search: do ... end do search)로 외부 루프 타겟팅 가능: 'exit search' 또는 'cycle search'. 중첩 루프를 깔끔하게 빠져나오는 데 필수. 필터링(원치 않는 반복 건너뛰기)에는 cycle, 조기 종료(검색 찾음, 오류 감지)에는 exit 사용. 명명된 루프는 중첩 제어 흐름을 명시적이고 읽기 쉽게 만듦.

fortran
program loop_control
  implicit none
  integer :: i, j

  ! cycle: skip to next iteration (like 'continue' in C)
  do i = 1, 10
    if (mod(i, 2) == 0) cycle   ! skip even numbers
    print *, i                  ! 1 3 5 7 9
  end do

  ! exit: break out of loop (like 'break' in C)
  do i = 1, 100
    if (i * i > 50) then
      print *, "Stopped at i=", i
      exit
    end if
  end do

  ! Named loops: cycle/exit can target outer loops
  search: do i = 1, 5
    do j = 1, 5
      if (i + j == 7) then
        print *, "Found: ", i, "+", j, "= 7"
        exit search             ! break out of OUTER loop
      end if
    end do
  end do search

  ! Early exit from a search
  integer :: arr(10) = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
  do i = 1, size(arr)
    if (arr(i) == 9) then
      print *, "Found 9 at index ", i
      exit
    end if
  end do
end program loop_control
03

배열 & 벡터 연산

배열 선언 & 초기화

Fortran 배열은 기본적으로 1-인덱스(하한 = 1), 하지만 사용자 정의 하한 지정 가능: a(0:4)는 인덱스 0..4. 다차원 배열은 (행, 열) 순서 — 열 우선 저장(첫 번째 인덱스가 메모리에서 가장 빨리 변화). 배열 생성자 [1,2,3]이나 implied-do [(expr, i=start,end)]로 초기화. reshape는 1D 목록에서 다차원 배열 채우기. size()는 총 요소 반환; lbound/ubound는 하한/상한 반환. shape()는 형상을 1D 배열로 반환. 배열은 '전체 배열' — 명시적 루프 없이 할당하고 연산 가능.

fortran
program array_decl
  implicit none
  ! Declaration with dimension
  integer :: a(5)                  ! 1D, indices 1..5
  integer :: b(0:4)                ! 1D, indices 0..4 (custom lower bound)
  real :: c(3, 4)                  ! 2D, 3 rows x 4 cols
  real, dimension(10) :: d         ! using dimension attribute

  ! Initialization at declaration
  integer :: x(5) = [1, 2, 3, 4, 5]
  integer :: y(5) = [(i*2, i=1,5)] ! implied-do: 2 4 6 8 10
  integer :: z(5) = 0              ! all zeros
  real :: m(2,2) = reshape([1,2,3,4], [2,2])

  ! Allocation later
  print *, size(x), lbound(x), ubound(x)   ! 5 1 5
  print *, size(c, dim=1)                  ! 3 (rows)
  print *, size(c, dim=2)                  ! 4 (cols)

  ! Array of characters
  character(10) :: names(3) = ["Alice", "Bob", "Carol"]
  print *, names(2)                        ! Bob
end program array_decl

배열 섹션 & 벡터 첨자

배열 섹션(슬라이싱)은 a(start:end:stride) 구문 사용 — 모든 부분 선택 사항. stride는 음수 가능(역순). 벡터 첨자는 인덱스 배열로 수집/분산 허용: a(idx)는 [a(idx(1)), a(idx(2)), ...] 반환. 섹션은 할당 가능: a(2:4) = [99,98,97]. 'where' 구문은 배열 수준 조건부 할당(numpy where처럼). Fortran의 배열 연산은 벡터화됨 — 요소별 연산에 명시적 루프 불필요. 이것이 수치 코드를 위한 Fortran의 킬러 기능: 깔끔하고 수학 같은 구문을 컴파일러가 자동 벡터화.

fortran
program array_sections
  implicit none
  integer :: a(10) = [(i, i=1,10)]
  integer :: b(5)
  integer :: idx(3) = [2, 5, 7]

  ! Array sections (slicing): a(start:end[:stride])
  print *, a(3:7)         ! 3 4 5 6 7
  print *, a(1:10:2)      ! 1 3 5 7 9 (stride 2)
  print *, a(:5)          ! 1 2 3 4 5 (start defaults to 1)
  print *, a(6:)          ! 6 7 8 9 10 (end defaults to ubound)
  print *, a(::3)         ! 1 4 7 10
  print *, a(10:1:-1)     ! 10 9 8 ... 1 (reverse)

  ! Vector subscript (gather)
  b = a(idx)              ! b = [a(2), a(5), a(7)] = [2, 5, 7]
  print *, b

  ! Assigning to a section
  a(2:4) = [99, 98, 97]
  print *, a(1:5)         ! 1 99 98 97 5

  ! 2D sections
  real :: m(4,4) = 0.0
  m(2:3, 2:3) = 1.0       ! set 2x2 sub-block
  print *, m(2,2), m(3,3) ! 1.0 1.0

  ! Where construct (masked array assignment)
  where (a > 50) a = 0    ! set elements > 50 to 0
end program array_sections

다차원 배열 & 행렬

Fortran은 배열을 열 우선으로 저장(첫 번째 인덱스가 메모리에서 가장 빨리 변화) — C와 반대. reshape는 열 우선 순서로 채우므로 reshape([1,2,3,4],[2,2])는 [[1,3],[2,4]] 제공. matmul(A,B)은 진정한 행렬 곱셈(선형 대수); A*B는 요소별(Hadamard) — 같지 않음! transpose(A)는 전치 반환. 축소: sum, product, maxval, minval, maxloc, minloc, count — 모두 dim=을 지원하여 한 축으로 축소. 고성능을 위해 열 우선 순서로 루프 작성(첫 번째 인덱스로 가장 안쪽 루프)하여 캐시 친화적으로.

fortran
program matrices
  implicit none
  real :: A(3,3), B(3,3), C(3,3)
  integer :: i, j

  ! Initialize with implied-do
  A = reshape([(real(i), i=1,9)], [3,3])
  ! A = 1 4 7
  !     2 5 8
  !     3 6 9   (column-major fill!)

  ! Identity matrix
  B = 0.0
  do i = 1, 3
    B(i,i) = 1.0
  end do

  ! Matrix multiplication (intrinsic)
  C = matmul(A, B)        ! A * I = A
  print *, C(1,1), C(2,2) ! 1.0 5.0

  ! Transpose
  print *, transpose(A)(1,:)   ! 1 2 3 (first row of A^T)

  ! Element-wise operations
  C = A + B               ! element-wise add
  C = A * 2.0             ! scalar multiply
  C = A * B               ! element-wise (NOT matmul!)

  ! Array reduction along a dimension
  print *, sum(A, dim=1)   ! column sums: 6 15 24
  print *, sum(A, dim=2)   ! row sums: 12 15 18
  print *, maxval(A)       ! 9.0
  print *, maxloc(A)       ! 3 3 (location of max)

  ! Reshape
  integer :: flat(6) = [1,2,3,4,5,6]
  integer :: mat(2,3)
  mat = reshape(flat, [2,3])
end program matrices

할당 가능 배열 (동적)

allocatable 배열은 Fortran에서 동적 메모리를 하는 현대적 방법 — 포인터보다 안전(메모리 누수 없음, 범위 종료 시 자동 할당 해제). allocatable 속성과 지연 형상((:, (:,:), 등)으로 선언. allocate()에 stat=로 오류 잡기(항상 확인!). deallocate()로 명시적 해제. Fortran 2003+은 할당 시 자동 재할당 지원: flex = [flex, 4]가 배열 성장. allocated()는 현재 할당되었는지 확인. 컴파일러가 추적하고 자동 해제하므로 동적 배열에는 포인터보다 allocatable이 선호 — 누수 없음, 매달린 포인터 없음.

fortran
program alloc_demo
  implicit none
  integer, allocatable :: arr(:), matrix(:,:)
  integer :: n, m, i, stat

  ! Get size from user
  print *, "Enter size:"
  read(*, *) n
  m = n * 2

  ! Allocate
  allocate(arr(n), matrix(n, m), stat=stat)
  if (stat /= 0) then
    print *, "Allocation failed!"
    stop 1
  end if

  ! Use the arrays
  arr = [(i, i=1, n)]
  matrix = 0.0
  do i = 1, n
    matrix(i, :) = i
  end do

  print *, size(arr), size(matrix, dim=2)
  print *, allocated(arr)   ! T

  ! Deallocate (or let it auto-deallocate at scope exit)
  deallocate(arr, matrix)
  print *, allocated(arr)   ! F

  ! Automatic reallocation on assignment (Fortran 2003)
  integer, allocatable :: flex(:)
  flex = [1, 2, 3]          ! auto-allocates to size 3
  flex = [flex, 4, 5]       ! reallocates to size 5: 1 2 3 4 5
  print *, flex
  deallocate(flex)
end program alloc_demo

배열 내장 함수

Fortran의 배열 내장 함수는 초능력. 축소: sum, product, maxval, minval, maxloc(최대값 인덱스), minloc, count(true 개수), any(존재), all(모두). 모두 mask=로 조건부 축소 지원. pack()은 mask가 true인 요소 수집(numpy compress처럼); unpack()은 분산. cshift/eoshift는 배열 회전(순환 vs 끝 오프). merge(a, b, mask)는 요소별 선택. 참고: Fortran에는 내장 정렬 없음 — 직접 작성(또는 라이브러리 사용). 이 내장 함수는 기본적이고 벡터화 가능하여 Fortran 코드를 깔끔하고 빠르게 만듦.

fortran
program array_funcs
  implicit none
  integer :: a(5) = [3, 1, 4, 1, 5, 9, 2, 6]
  ! Wait, let's fix size
  integer :: b(8) = [3, 1, 4, 1, 5, 9, 2, 6]
  integer :: c(5) = [10, 20, 30, 40, 50]
  logical :: mask(5) = [.true., .false., .true., .false., .true.]

  ! Inquiry
  print *, size(b)        ! 8
  print *, shape(b)       ! 8
  print *, lbound(b), ubound(b)   ! 1 8

  ! Reductions
  print *, sum(b)         ! 31
  print *, product(c)     ! 120000000
  print *, maxval(b), minval(b)   ! 9 1
  print *, maxloc(b)      ! 6 (index of max)
  print *, minloc(b)      ! 2 (index of min, first occurrence)
  print *, count(b > 3)   ! 5 (number of true elements)
  print *, any(b > 8)     ! T (at least one)
  print *, all(b > 0)     ! T (all of them)

  ! With mask
  print *, sum(b, mask=b > 3)     ! sum of elements > 3
  print *, pack(b, b > 3)         ! compact array of elements > 3
  print *, unpack([1,2], mask, 0) ! spread values per mask

  ! Manipulation
  print *, cshift(b, 2)   ! circular shift left by 2
  print *, eoshift(b, 2)  ! end-off shift left by 2 (fills 0)
  print *, merge(b, c, mask)  ! element-wise: b where mask true, else c

  ! Sorting (Fortran 2003+)
  integer :: sorted(8)
  sorted = b
  call sort_array(sorted)   ! custom sort (no built-in sort)
end program array_funcs
04

문자열 & 문자 처리

문자 선언 & 길이

Fortran 문자열은 기본적으로 고정 길이 — 더 짧은 문자열은 선언된 길이를 채우기 위해 공백으로 패딩. character(N) 또는 character(len=N)은 길이 N 선언. character(*)은 컨텍스트에서 길이 가져오기(매개변수 초기화나 더미 인수). character(:), allocatable은 지연 길이 동적 문자열 활성(Fortran 2003+) — 문자열이 할당 시 재할당. len()은 선언된 길이 반환; len_trim()은 후행 공백 없는 길이 반환. trim()은 후행 공백 없는 문자열 반환(하지만 결과는 컨텍스트에서 여전히 고정 길이). 가변 길이 텍스트 처리에는 allocatable 지연 길이 문자열 사용.

fortran
program char_decl
  implicit none
  ! Fixed-length strings
  character(10) :: s1 = "Hello"
  character(len=20) :: s2 = "World"
  character(20) :: s3          ! len= keyword optional

  ! Deferred-length (allocatable) - Fortran 2003+
  character(:), allocatable :: flex
  flex = "Dynamic"             ! len=7
  flex = "Now longer string"   ! reallocates to len=18
  print *, len(flex)           ! 18

  ! Array of strings
  character(15) :: names(3) = ["Alice", "Bob", "Carol"]

  ! Single character
  character(1) :: ch = 'A'
  character :: ch2 = 'B'       ! len=1 default

  print *, s1                  ! "Hello     " (padded to 10)
  print *, trim(s1)            ! "Hello" (no padding)
  print *, len(s1), len_trim(s1)   ! 10 5
  print *, names(2)            ! "Bob" (padded)
end program char_decl

문자열 연결 & 연산

문자열 연결은 // 연산자 사용. repeat(s, n)은 문자열을 n번 반복. 부분 문자열은 s(start:end) 사용 — 1-인덱스, 양 끝 포함(Python과 달리). s(8:)은 위치 8에서 끝까지; s(:5)는 시작에서 위치 5까지. index(s, sub)는 sub의 첫 번째 발생 위치 반환(없으면 0, 대소문자 구분). scan(s, set)은 set에 있는 첫 번째 문자 위치 반환; verify(s, set)은 set에 없는 첫 번째 문자 반환. adjustl/adjustr은 선행/후행 공백 이동. Fortran 문자열은 C처럼 null 종결이 아님 — 길이가 별도로 추적.

fortran
program string_ops
  implicit none
  character(20) :: first = "John", last = "Doe"
  character(50) :: full

  ! Concatenation with //
  full = first // " " // last   ! "John Doe"
  print *, trim(full)

  ! Repeat
  print *, repeat("-", 30)      ! 30 dashes

  ! Substring (1-indexed, inclusive)
  character(20) :: s = "Hello, World!"
  print *, s(1:5)               ! "Hello"
  print *, s(8:12)              ! "World"
  print *, s(8:)                ! "World!"  (to end)
  print *, s(:5)                ! "Hello"   (from start)

  ! Index (find substring)
  print *, index(s, "World")    ! 8 (position, 0 if not found)
  print *, index(s, "world")    ! 0 (case-sensitive)
  print *, scan(s, "aeiou")     ! 2 (first vowel position)
  print *, verify(s, "abcdefg") ! 1 (first char NOT in set)

  ! Length
  print *, len_trim(s)          ! 13
  print *, adjustl(s)           ! left-justify
  print *, adjustr(s)           ! right-justify
end program string_ops

내장 문자열 함수

iachar(c)는 문자의 ASCII 코드 반환; achar(i)는 역. (ichar/char은 프로세서 종속 — 이식성을 위해 iachar/achar 선호.) Fortran에는 내장 대소문자 변환 없음 — iachar/achar로 수동(A-Z는 65-90, a-z는 97-122, 차이는 32). 사전식 비교: lge/lgt/lle/llt(사전식으로 더 큰/작은)은 다른 길이의 문자열을 우아하게 처리. 내부 I/O(파일 대신 문자열로 읽기/쓰기)는 문자열과 숫자 간 변환의 관용적 방법: read(str, *) num과 write(str, fmt) num. I0 형식은 최소 너비 정수 제공(패딩 없음).

fortran
program string_funcs
  implicit none
  character(20) :: s = "Hello World"
  integer :: i
  character(1) :: ch

  ! Character code conversion
  print *, iachar('A')          ! 65 (ASCII code)
  print *, achar(66)            ! 'B' (from ASCII code)
  print *, ichar('A')           ! processor-dependent (use iachar for ASCII)

  ! Case conversion (manual - no built-in)
  do i = 1, len_trim(s)
    ch = s(i:i)
    if (ch >= 'A' .and. ch <= 'Z') then
      s(i:i) = achar(iachar(ch) + 32)   ! to lowercase
    end if
  end do
  print *, s                    ! "hello world"

  ! Comparison
  print *, lge("apple", "banana")   ! F (lexicographic >=)
  print *, lgt("zebra", "apple")    ! T
  print *, lle("abc", "abcd")       ! T (<=)
  print *, llt("abc", "abd")        ! T (<)

  ! String to number conversion (internal read)
  character(20) :: num_str = "3.14159"
  real :: pi_val
  read(num_str, *) pi_val
  print *, pi_val * 2           ! 6.28318

  ! Number to string (internal write)
  character(20) :: out_str
  integer :: n = 42
  write(out_str, '(I0)') n      ! I0 = minimal-width integer
  print *, "Number: " // trim(out_str)
end program string_funcs

문자열 서식

형식 사양은 문자열에 존재: '(I5, F8.2, A)'. I=정수, F=고정소수점 실수, E=지수, ES=과학(가수 1-10), A=문자, X=공백, /=줄바꿈. 너비 먼저(I5 = 너비 5), 그다음 선택적 .m for 최소 자릿수(I5.3). I0은 최소 너비(패딩 없음) 의미. ES는 적절한 과학 표기법(1.23E+6) 제공 vs E(0.12E+7). 반복: 3I4 = 각각 너비 4의 정수 3개. 형식의 문자열 리터럴: '("text")'. 형식은 문자열 변수, *(목록 지향, 컴파일러 선택), 또는 레이블(문장 번호)일 수 있음. 깔끔한 출력을 위해 정수에는 I0, 실수에는 F 또는 ES 선호.

fortran
program format_demo
  implicit none
  integer :: n = 42
  real :: pi = 3.14159265
  real :: big = 1234567.89
  character(20) :: name = "Alice"

  ! Format specifiers
  ! Iw     - integer, width w
  ! Iw.m   - integer, width w, at least m digits
  ! Fw.d   - fixed-point real, width w, d decimals
  ! Ew.d   - exponential, width w, d decimals
  ! Aw     - character, width w
  ! A      - character, default width
  ! nX     - n spaces
  ! /      - newline

  write(*, '(I5)') n            ! "   42"
  write(*, '(I5.3)') n          ! "  042"
  write(*, '(I0)') n            ! "42" (minimal)
  write(*, '(F10.4)') pi        ! "    3.1416"
  write(*, '(E12.4)') big       ! " 0.1235E+07"
  write(*, '(ES12.4)') big      ! " 1.2346E+06" (scientific)
  write(*, '(A10)') name        ! "     Alice"
  write(*, '(A, I3)') "n=", n   ! "n= 42"

  ! Multiple items
  write(*, '(A, I3, A, F8.4)') "n=", n, " pi=", pi

  ! Repeated format: 3I4 = three integers width 4
  write(*, '(3I4)') 1, 2, 3     ! "   1   2   3"

  ! Newline and spacing
  write(*, '("Name: ", A, /, "Age: ", I3)') name, n

  ! List-directed (default format)
  print *, name, n, pi
end program format_demo

파싱 & 토큰화

Fortran에는 내장 split/tokenize 없음 — index()와 부분 문자열로 수동 작성 필요. 패턴: index로 구분자 찾기, 부분 문자열로 토큰 추출, 구분자 지나기, 반복. 키=값 쌍의 경우 index로 '=' 찾고 키(이전)와 값(이후)으로 분할. trim()은 후행 공백 제거; adjustl()은 선행 공백 제거. 견고한 파싱을 위해 빈 토큰과 공백도 처리. 또는 구조화된 데이터에 형식 지정자로 내부 읽기 사용, 또는 파일처럼 문자열에서 읽기. split() 같은 라이브러리가 일부 Fortran 프레임워크에 존재하지만 표준은 아님.

fortran
program parse_demo
  implicit none
  character(100) :: line = "name=Bob,age=30,city=NYC"
  character(50) :: token
  integer :: pos, start, end

  ! Split by comma
  start = 1
  do
    end = index(line(start:), ",")
    if (end == 0) then
      ! last token
      token = line(start:)
      call process(trim(token))
      exit
    end if
    token = line(start:start+end-2)
    call process(trim(token))
    start = start + end
  end do

contains
  subroutine process(t)
    character(*), intent(in) :: t
    integer :: eq_pos
    eq_pos = index(t, "=")
    if (eq_pos > 0) then
      print *, "Key: ", trim(t(:eq_pos-1)), &
               " Value: ", trim(t(eq_pos+1:))
    end if
  end subroutine process
end program parse_demo

! Output:
! Key: name Value: Bob
! Key: age Value: 30
! Key: city Value: NYC
05

프로시저: 함수 & 서브루틴

함수

함수는 값을 반환하고 식에 사용(수학 함수처럼). 현대 구문: 'function name(args) result(var)' — result 변수가 반환되는 것. intent(in)은 읽기 전용 인수 표시(컴파일러가 강제). 함수는 스칼라 또는 배열 반환 가능(입력의 size()로 출력 크기 지정). 함수는 PURE여야 함(부작용 없음) — 함수에서 전역 상태 수정이나 I/O 하지 마세요. 내부 프로시저('contains' 블록)는 호스트의 변수에 접근(호스트 연관). 외부 프로시저의 경우 인터페이스 블록으로 시그니처 지정.

fortran
program func_demo
  implicit none
  ! Function declared in interface or as external
  print *, add(3, 4)         ! 7
  print *, square(5)         ! 25
  print *, distance(0.0, 0.0, 3.0, 4.0)   ! 5.0

contains
  ! Basic function with result clause
  function add(a, b) result(c)
    integer, intent(in) :: a, b
    integer :: c
    c = a + b
  end function add

  ! Function with result named same as function (legacy)
  function square(x) result(y)
    integer, intent(in) :: x
    integer :: y
    y = x * x
  end function square

  ! Real function
  function distance(x1, y1, x2, y2) result(d)
    real, intent(in) :: x1, y1, x2, y2
    real :: d
    d = sqrt((x2-x1)**2 + (y2-y1)**2)
  end function distance

  ! Array-valued function
  function reverse(arr) result(rev)
    integer, intent(in) :: arr(:)
    integer :: rev(size(arr))
    integer :: i, n
    n = size(arr)
    do i = 1, n
      rev(i) = arr(n-i+1)
    end do
  end function reverse
end program func_demo

서브루틴 & Intent

서브루틴은 'call'로 호출되고 값을 반환하지 않음 — 인수를 제자리에서 수정. 서브루틴 사용 시기: (1) 여러 인수 수정 필요, (2) 작업이 '계산'이 아닌 '명령', (3) 배열 값 결과 반환이 어색. intent 속성은 인수 방향 문서화 및 강제: intent(in) = 읽기 전용(할당 시 컴파일러 오류), intent(out) = 쓰기 전용(진입 시 정의되지 않음, 반환 전 설정 필요), intent(inout) = 읽기-쓰기. 항상 intent 지정 — 버그 잡고 최적화 활성화. 서브루틴은 전달된 배열 수정 가능(연속적이면 복사 없음).

fortran
program sub_demo
  implicit none
  integer :: x = 10, y = 20
  real :: arr(5) = [1.0, 2.0, 3.0, 4.0, 5.0]

  ! Subroutines are called with 'call'
  call swap(x, y)
  print *, x, y              ! 20 10

  call scale_array(arr, 2.0)
  print *, arr               ! 2 4 6 8 10

  call fill_zero(arr)
  print *, arr               ! 0 0 0 0 0

contains
  subroutine swap(a, b)
    integer, intent(inout) :: a, b   ! read AND write
    integer :: tmp
    tmp = a
    a = b
    b = tmp
  end subroutine swap

  subroutine scale_array(a, factor)
    real, intent(inout) :: a(:)
    real, intent(in) :: factor
    a = a * factor           ! whole-array operation
  end subroutine scale_array

  subroutine fill_zero(a)
    real, intent(out) :: a(:)        ! write-only (output)
    a = 0.0
  end subroutine fill_zero
end program sub_demo

순수 & 기본적 함수

pure 함수는 부작용 없음: I/O 없음, 전역 변수 수정 없음, stop 없음, 다른 pure 프로시저만 호출 가능. 컴파일러 최적화(병렬화, 공통 부분식 제거) 활성화하고 일부 컨텍스트(DO CONCURRENT 등)에서 필요. elemental 함수는 스칼라용으로 작성되지만 자동으로 배열에서 요소별 작동 — 한 번 작성, 둘 다 사용. pure elemental은 둘 다 결합. 진정한 수학 함수(부작용 없음)에는 pure 사용. 연산이 자연스럽게 배열에 요소별로 적용되는 경우(수학 함수, 변환)에는 elemental 사용. 컴파일러가 배열에서 elemental 호출을 자동 벡터화 가능.

fortran
program pure_demo
  implicit none
  real :: a(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
  real :: b(5)
  integer :: i

  ! Pure function: no side effects, no I/O
  b = square_arr(a)
  print *, b                 ! 1 4 9 16 25

  ! Elemental function: works on scalars AND arrays automatically
  b = cube(a)                ! applies cube() element-wise
  print *, b                 ! 1 8 27 64 125

  print *, cube(2.0)         ! also works on scalar: 8.0

contains
  ! Pure: no side effects, no I/O, no stop, only pure calls
  pure function square_arr(x) result(y)
    real, intent(in) :: x(:)
    real :: y(size(x))
    y = x * x
  end function square_arr

  ! Elemental: scalar signature, but works on arrays too
  elemental function cube(x) result(y)
    real, intent(in) :: x
    real :: y
    y = x * x * x
  end function cube

  ! Pure elemental: both
  pure elemental double precision function sq(x) result(y)
    double precision, intent(in) :: x
    double precision :: y
    y = x * x
  end function sq
end program pure_demo

선택적 & 키워드 인수

선택적 인수는 호출자가 생략 가능. 프로시저 내에서 present(arg)로 인수가 제공되었는지 확인 — 부재 선택적 접근은 정의되지 않은 동작. 키워드 인수(name="value")는 인수를 어떤 순서로든 전달 가능하고 호출을 자체 문서화. 키워드 사용 후 모든 후속 인수도 키워드 사용해야 함. 선택적 인수는 시그니처에서 모든 필수 인수 후에 와야 함. 기본값은 present() 검사로 구현(Fortran에는 내장 기본 구문 없음). 키워드 + 선택적으로 유연한 API 가능: 호출자는 필요한 것만 지정.

fortran
program optional_demo
  implicit none
  ! All arguments after the first optional must also be optional
  print *, greet("Alice")                    ! Hello, Alice!
  print *, greet("Bob", "Hi")                ! Hi, Bob!
  print *, greet("Carol", greeting="Hey")    ! keyword argument
  print *, greet(greeting="Welcome", name="Dave")  ! all keywords

  ! With present() check
  call log_msg("Starting up")
  call log_msg("Error!", level=2)
  call log_msg("Debug info", level=0, file="debug.log")

contains
  function greet(name, greeting) result(msg)
    character(*), intent(in) :: name
    character(*), intent(in), optional :: greeting
    character(50) :: msg
    character(20) :: g

    if (present(greeting)) then
      g = greeting
    else
      g = "Hello"
    end if
    msg = trim(g) // ", " // name // "!"
  end function greet

  subroutine log_msg(message, level, file)
    character(*), intent(in) :: message
    integer, intent(in), optional :: level
    character(*), intent(in), optional :: file
    integer :: lvl
    lvl = 1
    if (present(level)) lvl = level
    print *, "[L", lvl, "] ", trim(message)
  end subroutine log_msg
end program optional_demo

내부 & 재귀 프로시저

내부 프로시저('contains' 내)는 호스트 연관 — 호스트 프로그램의 변수를 읽고 수정 가능(클로저처럼). 호스트 상태가 필요한 헬퍼에 사용. 재귀 프로시저는 'recursive' 접두어로 선언 필요(Fortran 90/2003); Fortran 2018은 재귀를 기본으로. 상호 재귀의 경우 인터페이스 블록으로 전방 참조 선언. 재귀는 우아하지만 느릴 수 있음(함수 호출 오버헤드)하고 위험(깊은 재귀의 스택 오버플로우). 팩토리얼/피보나치의 경우 반복 버전이 더 빠르고 안전. 자연스럽게 재귀적인 문제(트리 순회, 분할 정복)에는 깊이가 제한된 재귀 사용.

fortran
program nested_demo
  implicit none
  integer :: counter = 0   ! host variable

  ! Internal procedures (in contains) access host variables
  call increment
  call increment
  print *, counter          ! 2

  ! Recursive function
  print *, factorial(5)     ! 120
  print *, fib(10)          ! 55

  ! Mutual recursion (needs forward declaration)
  print *, is_even(4)       ! T

contains
  subroutine increment
    counter = counter + 1   ! modifies host's counter
  end subroutine increment

  recursive function factorial(n) result(f)
    integer, intent(in) :: n
    integer :: f
    if (n <= 1) then
      f = 1
    else
      f = n * factorial(n-1)
    end if
  end function factorial

  recursive function fib(n) result(f)
    integer, intent(in) :: n
    integer :: f
    if (n < 2) then
      f = n
    else
      f = fib(n-1) + fib(n-2)
    end if
  end function fib

  ! Mutual recursion with interface
  recursive function is_even(n) result(r)
    integer, intent(in) :: n
    logical :: r
    interface
      recursive function is_odd(m) result(ro)
        integer, intent(in) :: m
        logical :: ro
      end function is_odd
    end interface
    if (n == 0) then
      r = .true.
    else
      r = is_odd(n-1)
    end if
  end function is_even
end program nested_demo
06

모듈 & 캡슐화

모듈 기본 & 사용

모듈은 Fortran의 주요 캡슐화 메커니즘(common blocks와 외부 프로시저 대체). 모듈 파일 포함: (1) 선언(상수, 변수, 파생 타입), (2) 프로시저가 있는 'contains' 블록. 'use module_name'으로 임포트; 'use module_name, only: x, y'는 특정 엔티티만 임포트(권장 — 이름 공간 오염 방지). 모듈 변수는 영구적(정적)이고 모듈을 사용하는 모든 프로시저에서 공유. 모듈은 명시적 인터페이스 제공(컴파일러가 인수 타입 검사), 외부 프로시저와 달리. 모듈을 사용하는 파일 전에 항상 모듈 파일 컴파일. 모듈의 'implicit none'은 모든 프로시저에 전파.

fortran
! geometry.f90 - module file
module geometry
  implicit none
  private                      ! default: everything private
  public :: circle_area, circle_perimeter, PI

  ! Module-level constants (persistent)
  real, parameter :: PI = 3.14159265

contains
  function circle_area(r) result(a)
    real, intent(in) :: r
    real :: a
    a = PI * r * r
  end function circle_area

  function circle_perimeter(r) result(p)
    real, intent(in) :: r
    real :: p
    p = 2.0 * PI * r
  end function circle_perimeter
end module geometry

! main.f90 - using the module
program use_module
  use geometry, only: circle_area, circle_perimeter, PI
  implicit none
  real :: r = 5.0
  print *, "Area: ", circle_area(r)         ! 78.5398
  print *, "Perimeter: ", circle_perimeter(r) ! 31.4159
  print *, "PI: ", PI
end program use_module

! Compile: gfortran geometry.f90 main.f90 -o main

접근 제어 (Public/Private)

접근 제어: 'private'는 엔티티를 모듈 내부로; 'public'은 내보냄. 모듈 수준에서 기본 설정 가능('private' 그다음 선택적으로 'public :: ...') — 이것이 모범 사례(명시적 인터페이스). 파생 타입 구성 요소는 타입 자체가 public이더라도 private 가능 — 호출자는 타입을 사용할 수 있지만 내부에 직접 접근 불가; 프로시저를 거쳐야 함. 'save'는 모듈 변수를 영속적으로(호출 간 값 유지) — 모듈 변수는 기본적으로 저장됨. 'final'은 소멸자 정의(객체가 범위를 벗어날 때 호출). 이 캡슐화는 프로시저를 통해 불변식이 강제되는 진정한 OOP를 가능하게.

fortran
module bank_account
  implicit none
  private                       ! default: everything private
  ! Explicitly export:
  public :: account_t, deposit, withdraw, get_balance

  ! Derived type - can expose type but hide internals
  type :: account_t
    private                    ! components are private
    real :: balance = 0.0
    integer :: id = 0
  contains
    procedure :: balance => get_bal   ! type-bound procedure
    final :: cleanup                  ! destructor
  end type account_t

  ! Module-level counter (private, not exported)
  integer, save :: next_id = 1000

contains
  ! Constructor (factory function)
  function create_account(initial) result(acc)
    type(account_t) :: acc
    real, intent(in) :: initial
    acc%balance = initial
    acc%id = next_id
    next_id = next_id + 1
  end function create_account

  subroutine deposit(acc, amount)
    type(account_t), intent(inout) :: acc
    real, intent(in) :: amount
    acc%balance = acc%balance + amount
  end subroutine deposit

  function get_bal(acc) result(b)
    class(account_t), intent(in) :: acc
    real :: b
    b = acc%balance
  end function get_bal

  subroutine cleanup(acc)
    type(account_t) :: acc
    ! cleanup code (e.g., log closure)
  end subroutine cleanup
end module bank_account

모듈의 파생 타입

모듈에 정의된 파생 타입은 가질 수 있음: 타입 바인딩 프로시저(procedure :: name => impl), 생성자(타입 이름으로 오버로드된 인터페이스), allocatable 구성 요소. 타입 바인딩 프로시저의 'class(keyword)'는 다형성 활성(실제 타입이 하위 클래스일 수 있음). 타입 바인딩 프로시저는 obj%method(args)로 호출 — OOP 구문. 타입 이름을 인터페이스로 오버로드하면 여러 생성자 가능(vector_from_array, vector_from_size). allocatable 구성 요소는 자동 할당/할당 해제. 이것이 현대 Fortran OOP: 모듈 시스템 내에서 캡슐화, 메서드, 생성자, 다형성.

fortran
module vector_mod
  implicit none
  private
  public :: vector_t, vector_add, vector_scale

  ! Derived type with type parameters (Fortran 2003)
  type :: vector_t
    real, allocatable :: data(:)
    integer :: length = 0
  contains
    procedure :: norm => vector_norm
    procedure :: print => vector_print
  end type vector_t

  ! Constructor interface (overloaded)
  interface vector_t
    procedure vector_from_array
    procedure vector_from_size
  end interface vector_t

contains
  function vector_from_array(arr) result(v)
    real, intent(in) :: arr(:)
    type(vector_t) :: v
    v%data = arr
    v%length = size(arr)
  end function

  function vector_from_size(n, fill) result(v)
    integer, intent(in) :: n
    real, intent(in) :: fill
    type(vector_t) :: v
    allocate(v%data(n))
    v%data = fill
    v%length = n
  end function

  function vector_norm(self) result(n)
    class(vector_t), intent(in) :: self
    real :: n
    n = sqrt(sum(self%data**2))
  end function

  subroutine vector_print(self)
    class(vector_t), intent(in) :: self
    print *, "Vector(len=", self%length, "): ", self%data
  end subroutine

  function vector_add(a, b) result(c)
    type(vector_t), intent(in) :: a, b
    type(vector_t) :: c
    c%data = a%data + b%data
    c%length = a%length
  end function

  function vector_scale(a, s) result(c)
    type(vector_t), intent(in) :: a
    real, intent(in) :: s
    type(vector_t) :: c
    c%data = a%data * s
    c%length = a%length
  end function
end module vector_mod

제네릭 프로시저 & 오버로딩

제네릭 인터페이스는 임시 다형성(오버로딩) 제공: 하나의 이름이 인수 타입에 따라 다른 특정 프로시저로 디스패치. 'interface name / module procedure proc1, proc2 / end interface' 블록은 모든 특정 프로시저 나열. 컴파일러가 컴파일 타임에 인수 타입/순위로 일치하는 것 선택. 제네릭의 모든 특정 프로시저는 구별 가능한 시그니처(인수 타입으로 구별 가능)를 가져야 함 — 그렇지 않으면 모호성. 이것이 Fortran이 템플릿 없이 연산자/함수 오버로딩을 하는 방식. 제네릭 디스패치는 컴파일 타임에 해결(런타임 오버헤드 없음). 타입 간 균일 API 제공에 제네릭 사용.

fortran
module generics_mod
  implicit none
  private
  public :: print_value, add

  ! Generic interface: one name, multiple specific procedures
  interface print_value
    module procedure print_int
    module procedure print_real
    module procedure print_str
    module procedure print_int_array
  end interface print_value

  interface add
    module procedure add_int
    module procedure add_real
    module procedure add_arrays
  end interface add

contains
  subroutine print_int(x)
    integer, intent(in) :: x
    print *, "Integer: ", x
  end subroutine

  subroutine print_real(x)
    real, intent(in) :: x
    print *, "Real: ", x
  end subroutine

  subroutine print_str(s)
    character(*), intent(in) :: s
    print *, "String: ", trim(s)
  end subroutine

  subroutine print_int_array(arr)
    integer, intent(in) :: arr(:)
    print *, "Array: ", arr
  end subroutine

  function add_int(a, b) result(c)
    integer, intent(in) :: a, b
    integer :: c
    c = a + b
  end function

  function add_real(a, b) result(c)
    real, intent(in) :: a, b
    real :: c
    c = a + b
  end function

  function add_arrays(a, b) result(c)
    real, intent(in) :: a(:), b(:)
    real :: c(size(a))
    c = a + b
  end function
end module generics_mod

program use_generics
  use generics_mod
  implicit none
  call print_value(42)              ! Integer: 42
  call print_value(3.14)            ! Real: 3.14
  call print_value("Hello")         ! String: Hello
  call print_value([1,2,3])         ! Array: 1 2 3
  print *, add(2, 3)                ! 5
  print *, add(2.5, 3.5)            ! 6.0
end program use_generics

연산자 오버로딩

연산자 오버로딩은 +, -, *, /, == 등이 파생 타입에서 어떻게 작동하는지 정의 가능. interface operator(+) / module procedure vec_add / end interface는 + 연산자를 함수에 바인딩. 이항 연산자의 경우 두 순서 모두 오버로드 가능(vec*scalar와 scalar*vec) 별도 프로시저로. assignment(=)는 할당 연산자 오버로드(프로시저는 intent(out) LHS와 intent(in) RHS를 가진 서브루틴). 이것은 수학 같은 구문 활성화: c = vec_add(a, b) 대신 c = a + b. 가독성을 향상시키는 수학 타입(벡터, 행렬, 복소수)에 연산자 오버로딩 사용. 명백하지 않은 의미에는 오버로딩 피하세요. 구조 생성자 vec3(x,y,z)는 파생 타입에 내장.

fortran
module vec_ops
  implicit none
  private
  public :: vec3, operator(+), operator(*), assignment(=)

  type :: vec3
    real :: x, y, z
  end type vec3

  ! Overload the + operator for vec3 + vec3
  interface operator(+)
    module procedure vec_add
  end interface

  ! Overload * for vec3 * scalar and scalar * vec3
  interface operator(*)
    module procedure vec_scale_r
    module procedure vec_scale_l
    module procedure vec_dot
  end interface

  ! Overload = for array-to-vec assignment
  interface assignment(=)
    module procedure arr_to_vec
  end interface

contains
  function vec_add(a, b) result(c)
    type(vec3), intent(in) :: a, b
    type(vec3) :: c
    c = vec3(a%x+b%x, a%y+b%y, a%z+b%z)
  end function

  function vec_scale_r(v, s) result(r)
    type(vec3), intent(in) :: v
    real, intent(in) :: s
    type(vec3) :: r
    r = vec3(v%x*s, v%y*s, v%z*s)
  end function

  function vec_scale_l(s, v) result(r)
    real, intent(in) :: s
    type(vec3), intent(in) :: v
    type(vec3) :: r
    r = vec3(v%x*s, v%y*s, v%z*s)
  end function

  ! vec3 * vec3 = dot product (scalar)
  function vec_dot(a, b) result(d)
    type(vec3), intent(in) :: a, b
    real :: d
    d = a%x*b%x + a%y*b%y + a%z*b%z
  end function

  subroutine arr_to_vec(v, arr)
    type(vec3), intent(out) :: v
    real, intent(in) :: arr(3)
    v = vec3(arr(1), arr(2), arr(3))
  end subroutine
end module vec_ops

program use_ops
  use vec_ops
  implicit none
  type(vec3) :: a, b, c
  a = vec3(1.0, 2.0, 3.0)
  b = vec3(4.0, 5.0, 6.0)
  c = a + b              ! vec3 addition
  print *, c%x, c%y, c%z ! 5 7 9
  print *, a * 2.0       ! scale: 2 4 6
  print *, 3.0 * b       ! scale: 12 15 18
  print *, a * b         ! dot product: 32
end program use_ops
07

파생 타입 (구조체) & OOP

파생 타입 정의

파생 타입은 Fortran의 구조체(사용자 정의 복합 타입). 'type :: Name ... end type Name'으로 정의. 구성 요소는 %로 접근(복소수의 .이 아님 — 그것은 복소수용). 구조 생성자: Name(val1, val2)로 인스턴스 생성. 구성 요소는 기본값을 가질 수 있음(선언에서 = value). 전체 타입 할당은 모든 구성 요소 복사(allocatable 구성 요소는 깊은 복사). 파생 타입의 배열 지원. 파생 타입은 Fortran에서 OOP의 기초(타입 바인딩 프로시저, 상속, 다형성과 함께). 구성 요소 접근에 % 사용: obj%field, obj%method(). 생성자 구문 Name(args)은 인터페이스로 override하지 않는 한 자동.

fortran
program derived_types
  implicit none
  ! Basic derived type (struct)
  type :: Point
    real :: x, y
  end type Point

  ! Type with default initialization
  type :: Person
    character(20) :: name = "Unknown"
    integer :: age = 0
    logical :: active = .true.
  end type Person

  ! Declare and construct
  type(Point) :: p1, p2
  type(Person) :: alice, bob

  ! Structure constructor
  p1 = Point(3.0, 4.0)
  p2 = Point(0.0, 0.0)
  alice = Person("Alice", 30, .true.)
  bob = Person("Bob", 25)        ! uses default for 'active'

  ! Access components with %
  print *, p1%x, p1%y            ! 3.0 4.0
  print *, alice%name, alice%age ! Alice 30
  print *, bob%active            ! T (default)

  ! Modify components
  p1%x = p1%x + 1.0
  alice%age = 31

  ! Whole-type assignment (component-wise copy)
  p2 = p1
  print *, p2%x                  ! 4.0

  ! Array of derived types
  type(Point) :: points(3)
  points(1) = Point(1.0, 1.0)
  points(2) = Point(2.0, 2.0)
  points(3) = Point(3.0, 3.0)
  print *, points(2)%y           ! 2.0
end program derived_types

타입 구성 요소 & 생성자

파생 타입은 가질 수 있음: allocatable 구성 요소(자동 관리 메모리), 기본 초기화된 구성 요소, 타입 바인딩 프로시저(메서드), finalizer(소멸자). 'interface TypeName / module procedure custom_init / end interface'는 구조 생성자를 사용자 정의 팩토리 함수로 오버로드. 타입 바인딩 프로시저의 'class(ClassName)'('type(ClassName)' vs)는 다형성 활성(실제 타입이 하위 클래스일 수 있음). 'final' 프로시저는 객체가 범위를 벗어날 때 실행(소멸자) — 리소스 해제에 사용. allocatable 구성 요소는 finalization 시 자동 할당 해제, 하지만 복잡한 정리에는 명시적 finalizer가 더 명확. block 구문은 코드 중간에 변수 선언 허용(Fortran 2008).

fortran
program type_components
  implicit none
  ! Type with various component kinds
  type :: Student
    integer :: id
    character(20) :: name
    real, allocatable :: grades(:)    ! allocatable component
    integer :: num_grades = 0
  contains
    procedure :: add_grade
    procedure :: average => student_avg
    final :: student_finalize
  end type Student

  ! Overloaded constructor
  interface Student
    module procedure student_init
  end interface Student

  type(Student) :: s

  ! Use custom constructor
  s = Student(101, "Alice")
  call s%add_grade(85.0)
  call s%add_grade(92.0)
  call s%add_grade(78.0)
  print *, s%average()    ! 85.0

contains
  function student_init(id, name) result(s)
    integer, intent(in) :: id
    character(*), intent(in) :: name
    type(Student) :: s
    s%id = id
    s%name = name
    s%num_grades = 0
  end function

  subroutine add_grade(self, g)
    class(Student), intent(inout) :: self
    real, intent(in) :: g
    integer :: n
    n = self%num_grades
    if (n == 0) then
      allocate(self%grades(1))
    else
      ! grow array
      block
        real, allocatable :: tmp(:)
        tmp = self%grades
        deallocate(self%grades)
        allocate(self%grades(n+1))
        self%grades(1:n) = tmp
      end block
    end if
    self%num_grades = n + 1
    self%grades(n+1) = g
  end subroutine

  function student_avg(self) result(avg)
    class(Student), intent(in) :: self
    real :: avg
    if (self%num_grades > 0) then
      avg = sum(self%grades) / self%num_grades
    else
      avg = 0.0
    end if
  end function

  subroutine student_finalize(self)
    type(Student) :: self
    if (allocated(self%grades)) deallocate(self%grades)
  end subroutine
end program type_components

타입 바인딩 프로시저 (메서드)

타입 바인딩 프로시저는 Fortran의 메서드: 'procedure :: method_name => implementation'. obj%method(args)로 호출 — OOP 구문. 첫 번째 인수는 'self'(객체), 'class(TypeName)'(다형적) 또는 'type(TypeName)'(구체적)으로 선언. 'class'는 상속/다형성 허용; 'type'은 확장 불가 타입용. '=> implementation'은 메서드 이름을 특정 프로시저에 매핑(이름 변경 허용). final은 소멸자. 이 예제는 자동 성장 배열로 동적 스택 구현. 타입 바인딩 프로시저는 진정한 OOP 제공: 캡슐화(데이터 + 메서드 함께), 메시지 전달 구문(obj%method), 다형성(class 통해). 미래 상속을 활성화하기 위해 타입 바인딩 프로시저에 항상 class() 사용.

fortran
module stack_mod
  implicit none
  private
  public :: stack_t

  type :: stack_t
    integer, allocatable :: data(:)
    integer :: top = 0
    integer :: capacity = 0
  contains
    procedure :: push => stack_push
    procedure :: pop => stack_pop
    procedure :: peek => stack_peek
    procedure :: is_empty => stack_is_empty
    procedure :: size => stack_size
    procedure :: clear => stack_clear
    final :: stack_finalize
  end type stack_t

  interface stack_t
    module procedure stack_init
  end interface

contains
  function stack_init(initial_cap) result(s)
    integer, intent(in), optional :: initial_cap
    type(stack_t) :: s
    integer :: cap
    cap = 16
    if (present(initial_cap)) cap = initial_cap
    allocate(s%data(cap))
    s%capacity = cap
    s%top = 0
  end function

  subroutine stack_push(self, val)
    class(stack_t), intent(inout) :: self
    integer, intent(in) :: val
    if (self%top >= self%capacity) then
      ! grow
      block
        integer, allocatable :: tmp(:)
        tmp = self%data
        deallocate(self%data)
        allocate(self%data(self%capacity * 2))
        self%data(1:self%capacity) = tmp
        self%capacity = self%capacity * 2
      end block
    end if
    self%top = self%top + 1
    self%data(self%top) = val
  end subroutine

  function stack_pop(self) result(val)
    class(stack_t), intent(inout) :: self
    integer :: val
    if (self%top == 0) stop "Stack underflow"
    val = self%data(self%top)
    self%top = self%top - 1
  end function

  function stack_peek(self) result(val)
    class(stack_t), intent(in) :: self
    integer :: val
    val = self%data(self%top)
  end function

  logical function stack_is_empty(self) result(r)
    class(stack_t), intent(in) :: self
    r = (self%top == 0)
  end function

  integer function stack_size(self) result(n)
    class(stack_t), intent(in) :: self
    n = self%top
  end function

  subroutine stack_clear(self)
    class(stack_t), intent(inout) :: self
    self%top = 0
  end subroutine

  subroutine stack_finalize(self)
    type(stack_t) :: self
    if (allocated(self%data)) deallocate(self%data)
  end subroutine
end module stack_mod

program use_stack
  use stack_mod
  implicit none
  type(stack_t) :: s
  s = stack_t(8)         ! initial capacity 8
  call s%push(10)
  call s%push(20)
  call s%push(30)
  print *, s%size()      ! 3
  print *, s%pop()       ! 30
  print *, s%pop()       ! 20
  print *, s%is_empty()  ! F
end program use_stack

상속 & 다형성

Fortran OOP: 'type, extends(Parent) :: Child'는 하위 클래스 생성(상속). 'type, abstract :: Name'과 'procedure(...), deferred :: method'는 추상 기반 정의(Java 추상 클래스 / C++ 순수 가상처럼). 'class(Base)'는 다형적 — 모든 하위 클래스 보유 가능. 다형적 디스패치: obj%method() 호출은 하위 클래스의 override 호출. 'select type (var => expr) / type is (ConcreteType) / end select'는 런타임 타입 검사(다운캐스팅). 'allocate(TypeName::var)'로 특정 구체적 타입의 다형적 객체 생성. 이것은 완전한 OOP: 상속, 다형성, 추상 타입, 런타임 디스패치 — Java/C++와 비교 가능.

fortran
module shapes
  implicit none
  private
  public :: shape, circle, rectangle, shape_ptr

  ! Base type (abstract)
  type, abstract :: shape
    character(20) :: name
  contains
    procedure(area_if), deferred :: area
    procedure(describe_if), deferred :: describe
    procedure :: get_name => shape_get_name
  end type shape

  ! Abstract interface (must be implemented by subclasses)
  abstract interface
    function area_if(self) result(a)
      import :: shape
      class(shape), intent(in) :: self
      real :: a
    end function area_if
    subroutine describe_if(self)
      import :: shape
      class(shape), intent(in) :: self
    end subroutine describe_if
  end interface

  ! Derived type: circle extends shape
  type, extends(shape) :: circle
    real :: radius
  contains
    procedure :: area => circle_area
    procedure :: describe => circle_describe
  end type circle

  ! Derived type: rectangle extends shape
  type, extends(shape) :: rectangle
    real :: width, height
  contains
    procedure :: area => rect_area
    procedure :: describe => rect_describe
  end type rectangle

  ! Class pointer for polymorphism
  type :: shape_ptr
    class(shape), allocatable :: ptr
  end type shape_ptr

contains
  function shape_get_name(self) result(n)
    class(shape), intent(in) :: self
    character(20) :: n
    n = self%name
  end function

  function circle_area(self) result(a)
    class(circle), intent(in) :: self
    real :: a
    a = 3.14159265 * self%radius**2
  end function

  subroutine circle_describe(self)
    class(circle), intent(in) :: self
    print *, "Circle '", trim(self%name), "' r=", self%radius
  end subroutine

  function rect_area(self) result(a)
    class(rectangle), intent(in) :: self
    real :: a
    a = self%width * self%height
  end function

  function rect_describe(self) result(s)
    class(rectangle), intent(in) :: self
    character(50) :: s
    write(s, '("Rectangle ", F6.2, "x", F6.2)') self%width, self%height
  end function
end module shapes

program use_polymorphism
  use shapes
  implicit none
  type(shape_ptr) :: shapes_arr(2)
  ! Allocate concrete types into polymorphic container
  allocate(circle::shapes_arr(1)%ptr)
  select type(s => shapes_arr(1)%ptr)
  type is (circle)
    s%radius = 5.0
    s%name = "C1"
  end select

  allocate(rectangle::shapes_arr(2)%ptr)
  select type(s => shapes_arr(2)%ptr)
  type is (rectangle)
    s%width = 3.0
    s%height = 4.0
    s%name = "R1"
  end select

  ! Polymorphic dispatch
  block
    integer :: i
    do i = 1, 2
      call shapes_arr(i)%ptr%describe
      print *, "Area: ", shapes_arr(i)%ptr%area()
    end do
  end block
end program use_polymorphism

중첩 타입 & 타입의 배열

파생 타입은 중첩 가능(조합): 타입은 다른 파생 타입의 구성 요소를 가질 수 있음. 연결된 %로 중첩 구성 요소 접근: emp%home%city. 구조 생성자는 자연스럽게 중첩: Employee(id, name, Address(...), salary). 파생 타입의 배열 지원: type(Employee) :: emps(N). 단일 구성 요소의 배열 추출 가능: emps(:)%id는 정수 배열 제공. allocatable 구성 요소는 동적 크기 컬렉션 허용(예: 가변 수의 직원이 있는 부서). 이 조합 모델은 Fortran에서 복잡한 데이터 구조(트리, 그래프, 리스트)를 구축하는 기초. 명확한 하위 타입 관계가 없을 때 상속(is-a)보다 조합(has-a)을 사용.

fortran
program nested_types
  implicit none
  ! Nested derived types
  type :: Address
    character(50) :: street
    character(30) :: city
    character(10) :: zip
  end type Address

  type :: Employee
    integer :: id
    character(30) :: name
    type(Address) :: home      ! nested type
    real :: salary
  end type Employee

  type :: Department
    character(30) :: name
    type(Employee), allocatable :: employees(:)   ! array of types
    integer :: count = 0
  end type Department

  ! Construct with nested structure constructor
  type(Employee) :: emp
  emp = Employee(101, "Alice", &
        Address("123 Main St", "Springfield", "12345"), 75000.0)

  ! Access nested components
  print *, emp%name                      ! Alice
  print *, emp%home%city                 ! Springfield
  print *, emp%home%zip                  ! 12345

  ! Department with array of employees
  type(Department) :: dept
  dept%name = "Engineering"
  allocate(dept%employees(3))
  dept%employees(1) = emp
  dept%employees(2) = Employee(102, "Bob", &
                        Address("456 Oak Ave", "Shelbyville", "54321"), 68000.0)
  dept%count = 2

  ! Iterate over array of types
  block
    integer :: i
    do i = 1, dept%count
      print *, dept%employees(i)%id, trim(dept%employees(i)%name), &
               dept%employees(i)%home%city
    end do
  end block

  ! Array of derived type components
  print *, dept%employees(1:2)%id        ! array of id field
end program nested_types
08

파일 I/O & 서식

파일 열기 & 닫기

open()은 파일을 유닛 번호에 연결. newunit=u는 컴파일러가 고유한 유닛을 선택(충돌 방지) — 하드코딩된 유닛 번호보다 항상 이것을 선호. status: 'old'(파일이 존재해야 함), 'new'(존재하지 않아야 함), 'replace'(삭제 + 생성), 'scratch'(임시, 닫을 때 자동 삭제). action: 'read', 'write', 'readwrite'. position: 'rewind'(시작), 'append'(끝), 'asis'(어디든). open과 read 후 항상 iostat 확인 — 0이 아닌 값은 오류 의미(음수 = EOF, 양수 = 오류). iomsg는 설명 오류 메시지 제공. close()는 연결 해제. 견고한 파일 처리를 위해 항상 iostat 확인하고 오류를 우아하게 처리.

fortran
program file_open
  implicit none
  integer :: u, ios
  character(100) :: msg

  ! newunit: compiler picks a unique unit number (Fortran 2008)
  ! status: 'old' (must exist), 'new' (must not exist),
  !         'replace' (overwrite), 'scratch' (temporary)
  ! action: 'read', 'write', 'readwrite'
  ! position: 'rewind', 'append', 'asis'
  open(newunit=u, file="data.txt", status="replace", &
       action="write", iostat=ios, iomsg=msg)
  if (ios /= 0) then
    print *, "Open failed: ", trim(msg)
    stop 1
  end if

  write(u, *) "First line"
  write(u, *) "Second line"
  close(u)

  ! Append to existing file
  open(newunit=u, file="data.txt", status="old", &
       action="write", position="append", iostat=ios)
  if (ios == 0) then
    write(u, *) "Appended line"
    close(u)
  end if

  ! Scratch file (auto-deleted on close)
  open(newunit=u, status="scratch", action="readwrite")
  write(u, *) "Temporary data"
  rewind(u)
  ! read back...
  close(u)   ! file disappears

  ! Reading with end-of-file detection
  open(newunit=u, file="data.txt", status="old", action="read")
  do
    read(u, '(A)', iostat=ios) msg
    if (ios /= 0) exit   ! EOF or error
    print *, trim(msg)
  end do
  close(u)
end program file_open

서식 있는 I/O

형식 지정자가 I/O 제어: '(A, I0, F8.2)'. A=문자, I0=정수 최소 너비, F8.2=실수 너비 8 소수 2자리. write(unit, fmt)는 쓰기; read(unit, fmt)는 읽기. unit=*는 stdout/stdin 의미. 파일의 경우 open()에서 유닛 사용. 목록 지향 I/O(*)는 유연: read(u, *) a, b, c는 쉼표/공백으로 구분된 값을 자동 읽기. CSV의 경우 값이 쉼표로 구분되면 목록 지향 읽기 작동. 형식 문자열 재사용 가능: '(3I4)'는 I4를 세 번 적용. 항상 형식을 데이터 타입에 일치 — 불일치 형식은 런타임 오류 발생. 혼합 텍스트+숫자의 경우 문자열로 읽은 후 파싱, 또는 명시적 형식 사용.

fortran
program formatted_io
  implicit none
  integer :: u, n = 42
  real :: pi = 3.14159265
  character(20) :: name = "Alice"

  ! Write formatted data to file
  open(newunit=u, file="output.txt", status="replace")
  write(u, '(A, I0)') "Count: ", n
  write(u, '(A, F8.4)') "Pi: ", pi
  write(u, '(A, A)') "Name: ", trim(name)
  write(u, '(3(I0, 1X))') 1, 2, 3   ! "1 2 3 "
  close(u)

  ! Read formatted data back
  open(newunit=u, file="output.txt", status="old", action="read")
  block
    character(100) :: line
    integer :: i
    do i = 1, 5
      read(u, '(A)') line
      print *, trim(line)
    end do
  end block
  close(u)

  ! Reading structured data
  open(newunit=u, file="data.csv", status="replace")
  write(u, '(I0, ",", I0, ",", I0)') 1, 10, 100
  write(u, '(I0, ",", I0, ",", I0)') 2, 20, 200
  close(u)

  ! Read back as numbers
  open(newunit=u, file="data.csv", status="old", action="read")
  block
    integer :: a, b, c
    ! List-directed read handles commas as separators
    read(u, *) a, b, c
    print *, a, b, c   ! 1 10 100
    read(u, *) a, b, c
    print *, a, b, c   ! 2 20 200
  end block
  close(u)
end program formatted_io

비서식 (바이너리) I/O

비서식(바이너리) I/O는 서식 있는(텍스트) I/O보다 빠르고 더 소형 — 문자열 변환 없음. form='unformatted'로 활성화. access='stream'(Fortran 2003)은 바이트 스트림 접근 제공(C 파일 I/O처럼, 레코드 마커 없음). access='sequential'(기본값)은 레코드 마커 사용(각 write/read는 길이 접두사가 있는 레코드) — Fortran 내에서는 이식 가능하지만 다른 언어에는 아님. C/Python과의 상호 운용성을 위해 스트림 접근 사용. 바이너리 파일은 사람이 읽을 수 없지만 대규모 수치 데이터셋에 이상적. 올바르게 읽을 수 있도록 데이터 전에 항상 메타데이터(배열 크기, 타입 정보) 작성. 비서식 I/O는 완전한 정밀도 보존(텍스트 변환에서 반올림 없음).

fortran
program binary_io
  implicit none
  integer :: u, i
  real :: arr(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
  real :: arr_in(5)
  integer :: n

  ! Write binary (unformatted) - faster, no format conversion
  open(newunit=u, file="data.bin", status="replace", &
       form="unformatted", access="stream")
  write(u) size(arr)        ! write the count first
  write(u) arr              ! write the whole array
  close(u)

  ! Read binary back
  open(newunit=u, file="data.bin", status="old", &
       form="unformatted", access="stream", action="read")
  read(u) n                 ! read the count
  print *, "Count: ", n
  read(u) arr_in            ! read the array
  print *, arr_in           ! 1 2 3 4 5
  close(u)

  ! Sequential unformatted (default) - with record markers
  open(newunit=u, file="data_seq.bin", status="replace", &
       form="unformatted")   ! access defaults to "sequential"
  do i = 1, 5
    write(u) i, arr(i)      ! each write is a "record"
  end do
  close(u)

  ! Read sequentially
  open(newunit=u, file="data_seq.bin", status="old", &
       form="unformatted", action="read")
  do
    read(u, iostat=i) n, arr_in(1)
    if (i /= 0) exit
    print *, n, arr_in(1)
  end do
  close(u)
end program binary_io

Namelist (그룹화된 I/O)

namelist는 구조화된 텍스트 I/O를 위해 변수를 그룹화 — JSON/YAML처럼 하지만 Fortran 네이티브. 'namelist /name/ var1, var2, ...'로 정의. write(u, nml=name)은 &NAME var=val, var=val, / 형식으로 출력. read(u, nml=name)은 다시 파싱. Namelist는 설정 파일에 완벽: 사용자가 텍스트 파일 편집, 프로그램이 읽기. 변수는 선언된 값을 기본값으로 유지; 파일에 있는 것만 재정의. 형식은 관대(공백 무감각, 선택적 쉼표). Namelist는 모든 내장 타입과 배열 지원. iostat는 파싱 오류 잡기. 이것은 사용자 정의 파서 작성 없이 Fortran 프로그램을 구성 가능하게 하는 가장 쉬운 방법.

fortran
program namelist_demo
  implicit none
  ! Namelist groups variables for easy I/O
  integer :: max_iter = 100
  real :: tolerance = 1.0e-6
  logical :: verbose = .true.
  character(20) :: method = "newton"
  real :: params(3) = [0.1, 0.2, 0.3]

  ! Define a namelist group
  namelist /config/ max_iter, tolerance, verbose, method, params

  ! Write namelist to file
  block
    integer :: u
    open(newunit=u, file="config.nml", status="replace")
    write(u, nml=config)
    close(u)
  end block

  ! The file looks like:
  ! &CONFIG
  !  MAX_ITER=100,
  !  TOLERANCE=1.0000000E-06,
  !  VERBOSE=T,
  !  METHOD="newton",
  !  PARAMS=0.100000, 0.200000, 0.300000,
  ! /

  ! Read namelist from file (overrides defaults)
  block
    integer :: u, ios
    open(newunit=u, file="config.nml", status="old", action="read")
    read(u, nml=config, iostat=ios)
    close(u)
    if (ios /= 0) then
      print *, "Error reading config"
    else
      print *, "max_iter=", max_iter
      print *, "tolerance=", tolerance
      print *, "method=", trim(method)
      print *, "params=", params
    end if
  end block

  ! User can edit config.nml in a text editor
  ! then re-run to pick up new values
end program namelist_demo

내부 파일 & 오류 처리

내부 파일은 파일 대신 문자열에서 읽고/쓰기 가능 — Fortran의 sprintf/sscanf. write(str, fmt)는 문자열로 서식; read(str, fmt)는 문자열에서 파싱. 이것이 문자열과 숫자 간 변환의 표준 방법. 오류 처리에는 항상 iostat 사용: 0 = 성공, 음수 = EOF, 양수 = 오류. 레거시 end=와 err= 레이블은 작동하지만 iostat가 더 깔끔함(goto 없음). 견고한 파싱을 위해 매 read 후 iostat 확인. 내부 I/O는 다음에 좋음: 출력 문자열 빌드, 사용자 입력 파싱, 설정 파일 값 변환. 문자열은 '내부 파일'로 작동 — 동일한 I/O 문, 단지 문자열 목적지.

fortran
program internal_io
  implicit none
  character(100) :: buffer
  integer :: n = 42
  real :: x = 3.14159
  integer :: ios

  ! Internal write: format to a string (like sprintf)
  write(buffer, '(A, I0, A, F8.4)') "n=", n, " x=", x
  print *, trim(buffer)   ! n=42 x=  3.1416

  ! Internal read: parse from a string (like sscanf)
  character(50) :: input = "100 3.14 hello"
  integer :: a
  real :: b
  character(20) :: c
  read(input, *, iostat=ios) a, b, c
  if (ios == 0) print *, a, b, trim(c)   ! 100 3.14 hello

  ! Robust number parsing with error handling
  character(20) :: num_str = "3.14abc"
  real :: val
  read(num_str, *, iostat=ios) val
  if (ios /= 0) then
    print *, "Parse error: '", trim(num_str), "' is not a number"
  else
    print *, "Value: ", val
  end if

  ! End-of-file and error handling on file reads
  block
    integer :: u
    character(100) :: line
    open(newunit=u, file="data.txt", status="old", action="read")
    do
      read(u, '(A)', iostat=ios) line
      if (ios < 0) then
        print *, "End of file"
        exit
      else if (ios > 0) then
        print *, "Read error at line"
        exit
      end if
      print *, trim(line)
    end do
    close(u)
  end block

  ! err= and end= labels (legacy style)
  block
    integer :: u, val
    open(newunit=u, file="nums.txt", status="old", action="read")
    do
      read(u, *, end=100, err=200) val
      print *, val
    end do
100 print *, "Reached EOF"
    close(u)
    goto 300
200 print *, "Read error!"
    close(u)
300 continue
  end block
end program internal_io
09

수치 컴퓨팅

Kind 매개변수 & 정밀도

Kind 매개변수는 정밀도/크기 제어. 현대 이식 가능한 방법: iso_fortran_env 사용(int32/int64, real32/real64/real128). 레거시: selected_real_kind(digits, exponent_range). real32 ≈ 7 유효 자릿수, real64 ≈ 15 자릿수(배정밀도), real128 ≈ 33 자릿수(쿼드). 중요: 항상 리터럴에 kind 접미사 추가(3.14_dp, 3.14가 아님) — 그렇지 않으면 리터럴이 단정밀도로 파싱된 후 변환되어 자릿수 손실. precision()은 유효 자릿수 반환; range()는 십진 지수 범위 반환. epsilon()은 기계 엡실론(가장 작은 구별 가능한 증분) 제공. tiny/huge는 최소/최대 제공. 과학 컴퓨팅의 경우 기본적으로 real64(배정밀도) 사용.

fortran
program precision_demo
  use iso_fortran_env, only: int32, int64, real32, real64, real128
  implicit none
  ! Portable kind selection via iso_fortran_env
  integer(kind=int32) :: i32 = 100
  integer(kind=int64) :: i64 = 9223372036854775807_int64
  real(kind=real32) :: r32 = 3.14159265_real32   ! single (~7 digits)
  real(kind=real64) :: r64 = 3.14159265358979_real64  ! double (~15 digits)
  real(kind=real128) :: r128 = 3.14159265358979_real128 ! quad (~33 digits)

  ! Legacy: selected_real_kind (still works)
  integer, parameter :: dp = selected_real_kind(15, 307)  ! double
  integer, parameter :: sp = selected_real_kind(6, 37)    ! single
  real(kind=dp) :: pi = 3.14159265358979_dp

  print *, "real32 precision: ", precision(r32), " range: ", range(r32)
  print *, "real64 precision: ", precision(r64), " range: ", range(r64)
  print *, "real128 precision: ", precision(r128)

  ! Epsilon and tiny
  print *, "epsilon(r32): ", epsilon(r32)   ! ~1.19e-7
  print *, "epsilon(r64): ", epsilon(r64)   ! ~2.22e-16
  print *, "tiny(r64): ", tiny(r64)         ! smallest positive
  print *, "huge(i32): ", huge(i32)         ! 2147483647
  print *, "huge(i64): ", huge(i64)

  ! Always use _kind suffix on literals
  ! WRONG: real(kind=dp) :: x = 3.14  (loses precision!)
  ! RIGHT: real(kind=dp) :: x = 3.14_dp
end program precision_demo

선형 대수 (matmul, solve, BLAS)

Fortran에는 내장 선형 대수: matmul(행렬-행렬/행렬-벡터 곱), dot_product, transpose. 선형 시스템(Ax=b), 고유값, SVD 등의 해결에는 LAPACK(업계 표준 Fortran 라이브러리) 사용: dgesv는 Ax=b 해결, dgesvd는 SVD, dsyev는 고유분해. -llapack -lblas로 링크. 예제는 3x3 시스템에 대한 수동 가우스 소거를 보여줌 — 실제 작업에는 LAPACK 사용(더 빠르고, 피벗팅으로 더 정확, 모든 크기 처리). Fortran의 열 우선 저장은 LAPACK의 기대와 자연스럽게 일치(전치 불필요). matmul은 최적화되었지만 큰 행렬의 경우 BLAS dgemm이 더 빠름. 수치 안정성을 위해 항상 조건수 확인.

fortran
program linalg
  implicit none
  real(8) :: A(3,3), B(3,3), C(3,3)
  real(8) :: v(3), w(3), x(3)
  real(8) :: det
  integer :: i

  ! Initialize matrices
  A = reshape([1,2,3, 4,5,6, 7,8,10], [3,3])  ! column-major fill
  B = reshape([1,0,0, 0,1,0, 0,0,1], [3,3])   ! identity

  ! Matrix-matrix multiplication
  C = matmul(A, B)        ! A * I = A
  print *, "A * I = A? ", all(abs(C - A) < 1e-10)

  ! Matrix-vector multiplication
  v = [1.0, 2.0, 3.0]
  w = matmul(A, v)
  print *, "A * v = ", w    ! 14 32 53

  ! Dot product
  print *, "v . v = ", dot_product(v, v)   ! 14

  ! Outer product
  C = 0.0
  do i = 1, 3
    C(:,i) = v * v(i)
  end do

  ! Transpose
  C = transpose(A)

  ! Solve linear system Ax = b (need LAPACK or custom)
  ! Using LAPACK dgesv:
  !   call dgesv(n, nrhs, A, lda, ipiv, b, ldb, info)
  ! For demo, manual Gaussian elimination:
  x = solve_3x3(A, v)
  print *, "Solution: ", x

contains
  function solve_3x3(A, b) result(x)
    real(8), intent(inout) :: A(3,3)
    real(8), intent(in) :: b(3)
    real(8) :: x(3), M(3,4), factor
    integer :: k, j
    M(:,1:3) = A
    M(:,4) = b
    ! Forward elimination
    do k = 1, 2
      do i = k+1, 3
        factor = M(i,k) / M(k,k)
        M(i,:) = M(i,:) - factor * M(k,:)
      end do
    end do
    ! Back substitution
    x(3) = M(3,4) / M(3,3)
    x(2) = (M(2,4) - M(2,3)*x(3)) / M(2,2)
    x(1) = (M(1,4) - M(1,2)*x(2) - M(1,3)*x(3)) / M(1,1)
  end function
end program linalg

난수

call random_number(x)는 x를 균등 [0,1) 실수로 채움 — 스칼라나 배열에서 작동. call random_seed(size=n)은 시드 크기 획득; random_seed(put=seed)는 재현성을 위해 시드 설정(테스트/디버깅에 필수). [a,b] 범위의 정수: a + int(r * (b-a+1)). 가우시안(정규) 난수의 경우 Box-Muller 변환(표시)이나 극좌표법 사용. Fortran에는 내장 정규 분포 생성기 없음 — 직접 구현하거나 라이브러리 사용. 몬테카를로 시뮬레이션의 경우 재현성을 위해 시드 설정, 그다음 많은 시도 실행. random_number는 암호학적으로 안전하지 않음 — 보안에는 암호 라이브러리 사용. 병렬 코드의 경우 각 이미지에 고유한 시드 필요.

fortran
program random_demo
  implicit none
  real :: r
  integer :: i, n = 10
  real :: arr(10)
  integer :: seed_size
  integer, allocatable :: seed(:)

  ! Initialize random seed (Fortran 2003: random_seed with no args
  ! uses a processor-dependent seed; for reproducibility, set it)
  call random_seed(size=seed_size)
  allocate(seed(seed_size))
  ! Set a fixed seed for reproducibility
  seed = [(12345 + i*6789, i=1, seed_size)]
  call random_seed(put=seed)

  ! Generate uniform [0,1) random reals
  call random_number(r)
  print *, "Single: ", r

  call random_number(arr)   ! fills entire array
  print *, "Array: ", arr

  ! Generate integers in [a, b]
  block
    integer :: a = 1, b = 6
    integer :: dice
    do i = 1, 5
      call random_number(r)
      dice = a + int(r * (b - a + 1))   ! [1, 6]
      print *, "Dice roll: ", dice
    end do
  end block

  ! Normal (Gaussian) via Box-Muller transform
  block
    real :: u1, u2, z1, z2
    integer :: j
    do j = 1, 5
      call random_number(u1)
      call random_number(u2)
      u1 = max(u1, 1e-10)   ! avoid log(0)
      z1 = sqrt(-2.0 * log(u1)) * cos(2.0 * 3.14159265 * u2)
      z2 = sqrt(-2.0 * log(u1)) * sin(2.0 * 3.14159265 * u2)
      print *, "Gaussian: ", z1, z2
    end do
  end block

  ! Monte Carlo: estimate pi
  block
    integer :: inside = 0, total = 100000
    real :: x, y
    do i = 1, total
      call random_number(x)
      call random_number(y)
      if (x*x + y*y <= 1.0) inside = inside + 1
    end do
    print *, "Pi estimate: ", 4.0 * real(inside) / total
  end block
end program random_demo

수치 적분 & 근 찾기

수치 적분: 심프슨 규칙은 사다리꼴 규칙보다 더 정확(O(h^4) 오류 vs O(h^2)). 인터페이스 블록을 사용하여 함수를 인수로 전달. 근 찾기: 이분법은 견고(부호가 바뀌면 항상 수렴)하지만 느림(선형 수렴); 뉴턴법은 빠름(이차 수렴)하지만 도함수가 필요하고 발산 가능. 프로덕션 작업에는 QUADPACK(적분)이나 MINPACK(근 찾기) 사용 — 실전 검증된 Fortran 라이브러리. 인터페이스 블록은 함수를 인수로 전달할 때 필수 — 컴파일러에게 함수의 시그니처 알림. 무한 루프 방지를 위해 항상 최대 반복 횟수 설정. 함수 값과 단계 크기 허용 오차 모두로 수렴 확인.

fortran
program numerical
  implicit none
  real(8) :: a, b, result
  integer :: n

  ! Numerical integration: Simpson's rule
  ! Integrate f(x) = x^2 from 0 to 2 (exact: 8/3 ≈ 2.6667)
  a = 0.0; b = 2.0; n = 1000
  result = simpson(f_sq, a, b, n)
  print *, "Integral of x^2 from 0 to 2: ", result   ! ~2.6667

  ! Integrate sin(x) from 0 to pi (exact: 2.0)
  result = simpson(f_sin, 0.0_8, 3.14159265358979_8, 1000)
  print *, "Integral of sin(x) from 0 to pi: ", result  ! ~2.0

  ! Root finding: bisection method
  ! Find root of f(x) = x^2 - 2 (i.e., sqrt(2) ≈ 1.4142)
  result = bisection(f_x2_minus_2, 0.0_8, 2.0_8, 1e-12_8)
  print *, "sqrt(2) = ", result   ! ~1.41421356

  ! Newton's method (needs derivative)
  result = newton(f_x2_minus_2, fp_x2_minus_2, 1.0_8, 1e-12_8)
  print *, "sqrt(2) via Newton: ", result

contains
  ! Function to integrate: x^2
  function f_sq(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = x * x
  end function

  function f_sin(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = sin(x)
  end function

  ! Simpson's rule: integral of f from a to b with n intervals
  function simpson(f, a, b, n) result(integral)
    interface
      function f(x) result(y)
        import
        real(8), intent(in) :: x
        real(8) :: y
      end function
    end interface
    real(8), intent(in) :: a, b
    integer, intent(in) :: n
    real(8) :: integral, h, x
    integer :: i
    h = (b - a) / n
    integral = f(a) + f(b)
    do i = 1, n-1
      x = a + i * h
      if (mod(i, 2) == 0) then
        integral = integral + 2.0 * f(x)
      else
        integral = integral + 4.0 * f(x)
      end if
    end do
    integral = integral * h / 3.0
  end function

  function f_x2_minus_2(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = x*x - 2.0
  end function

  function fp_x2_minus_2(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = 2.0 * x
  end function

  ! Bisection: find root in [a, b] (f(a) and f(b) must have opposite signs)
  function bisection(f, a, b, tol) result(root)
    interface
      function f(x) result(y)
        import
        real(8), intent(in) :: x
        real(8) :: y
      end function
    end interface
    real(8), intent(in) :: a, b, tol
    real(8) :: root, fa, fb, mid, fmid
    integer :: iter, maxiter = 100
    fa = f(a); fb = f(b)
    if (fa * fb > 0) stop "No sign change in interval"
    do iter = 1, maxiter
      mid = (a + b) / 2.0
      fmid = f(mid)
      if (abs(fmid) < tol .or. (b - a)/2.0 < tol) then
        root = mid
        return
      end if
      if (fa * fmid < 0) then
        b = mid; fb = fmid
      else
        a = mid; fa = fmid
      end if
    end do
    root = (a + b) / 2.0
  end function

  ! Newton's method: x_{n+1} = x_n - f(x)/f'(x)
  function newton(f, fp, x0, tol) result(root)
    interface
      function f(x) result(y)
        import
        real(8), intent(in) :: x
        real(8) :: y
      end function
      function fp(x) result(y)
        import
        real(8), intent(in) :: x
        real(8) :: y
      end function
    end interface
    real(8), intent(in) :: x0, tol
    real(8) :: root, x, fx, fpx
    integer :: iter, maxiter = 100
    x = x0
    do iter = 1, maxiter
      fx = f(x); fpx = fp(x)
      if (abs(fx) < tol) then
        root = x
        return
      end if
      x = x - fx / fpx
    end do
    root = x
  end function
end program numerical

IEEE 산술 & 예외

ieee_arithmetic 모듈(Fortran 2003)은 IEEE 754 지원 제공: 무한대, NaN(Not a Number), 신호/조용한 NaN, 예외 플래그, 반올림 모드. NaN은 어떤 것과도 같지 않음(자신 포함) — ieee_is_nan()로 테스트. 무한대는 오버플로우나 0으로 나누기에서 발생. 예외 플래그(ieee_divide_by_zero, ieee_overflow, ieee_underflow, ieee_inexact, ieee_invalid)는 예외 발생 추적 — ieee_get_flag로 확인, ieee_set_flag로 지움. ieee_set_halting_mode는 예외가 프로그램을 중단할지 제어. 반올림 모드는 부동소수점 연산에 영향. 견고한 수치 코드에 사용: NaN/Inf 감지, 예외 우아하게 처리, 정밀도 제어. 참고: gfortran의 -ffast-math는 IEEE 준수 위반(이 기능에 의존하는 코드에 사용 금지).

fortran
program ieee_demo
  use ieee_arithmetic
  implicit none
  real :: a, b, c
  logical :: flag

  ! IEEE special values
  a = ieee_value(a, ieee_positive_inf)   ! +Infinity
  b = ieee_value(b, ieee_negative_inf)   ! -Infinity
  c = ieee_value(c, ieee_quiet_nan)      ! NaN

  print *, "Infinity: ", a                ! Infinity
  print *, "NaN: ", c                     ! NaN
  print *, "Inf > 1e30: ", a > 1e30       ! T
  print *, "NaN == NaN: ", c == c         ! F (NaN is never equal!)

  ! Check for special values
  print *, "is_nan(c): ", ieee_is_nan(c)         ! T
  print *, "is_finite(1.0): ", ieee_is_finite(1.0)  ! T
  print *, "is_inf(a): ", ieee_is_finite(a)      ! F

  ! Operations producing special values
  print *, "1.0/0.0: ", 1.0/0.0           ! Infinity (if -ffast-math off)
  print *, "0.0/0.0: ", 0.0/0.0           ! NaN
  print *, "sqrt(-1.0): ", sqrt(-1.0)     ! NaN

  ! IEEE exception flags
  call ieee_set_halting_mode(ieee_divide_by_zero, .false.)  ! don't halt
  b = 1.0 / 0.0   ! sets divide_by_zero flag, returns Inf
  call ieee_get_flag(ieee_divide_by_zero, flag)
  print *, "Divide by zero occurred: ", flag   ! T

  ! Check and clear flags
  call ieee_set_flag(ieee_all, .false.)   ! clear all flags
  b = 1.0 / 0.0
  call ieee_get_flag(ieee_divide_by_zero, flag)
  print *, "Flag after division: ", flag   ! T
  call ieee_set_flag(ieee_all, .false.)   ! clear

  ! Rounding modes
  call ieee_set_rounding_mode(ieee_nearest)   ! default
  call ieee_set_rounding_mode(ieee_down)      ! round toward -inf
  call ieee_set_rounding_mode(ieee_up)        ! round toward +inf
  call ieee_set_rounding_mode(ieee_to_zero)   ! truncate

  ! Comparing NaN-safe
  if (ieee_unordered(c, 1.0)) print *, "c is unordered (NaN)"
end program ieee_demo
10

Coarray & 병렬

기본 coarray 선언

Coarray는 Fortran의 내장 병렬 모델(F2008). 각 '이미지'는 병렬 프로세스. [*] 접미사로 선언. this_image()는 순위 반환; num_images()는 개수. x[k]로 원격 접근. sync all은 장벽. 컴파일러: gfortran(-fcoarray=lib 사용), ifort, Cray.

fortran
program coarray_hello
  use iso_fortran_env, only: real64
  implicit none
  real(real64) :: x[*]  ! coarray — one copy per image
  integer :: me

  me = this_image()
  x = real(me, real64)  ! local assignment

  call co_sum(x, result_image=1)  ! reduce sum to image 1
  if (me == 1) print *, 'Sum =', x

  sync all  ! barrier
end program

원격 접근과 동기화

[k] 접미사로 원격 coarray 접근 — 단방향 통신. 읽기와 쓰기는 동기화될 때까지 비차단. sync all은 전역 장벽; sync images([1,2])는 특정 이미지 대기. 임계 섹션: critical...end critical로 lock/unlock. 일관되게 동기화 순서를 지정하여 교착 상태 피하기.

fortran
program remote_access
  implicit none
  integer :: val[*], neighbor
  integer :: me, n

  me = this_image()
  n = num_images()
  val = me * 10

  sync all  ! ensure all writes complete

  ! read from neighbor (circular)
  neighbor = merge(1, me + 1, me == n)
  print *, 'Image', me, 'sees neighbor', neighbor, 'value', val[neighbor]

  ! write to image 1 from all
  if (me /= 1) val[1] = val[me]
  sync all
  if (me == 1) print *, 'Image 1 received:', val
end program

집합 연산

집합: co_sum, co_min, co_max, co_broadcast. coarray에서 작동하고 이미지 간 축소/브로드캐스트. result_image는 답을 받을 사람 지정(기본값: 모두). 브로드캐스트는 source_image. 다른 이미지에서 집합 결과 읽기 전에 항상 동기화. 동기화가 있는 수동 루프보다 빠름.

fortran
program collectives
  use iso_fortran_env, only: real64
  implicit none
  real(real64) :: local_sum, global_sum[*]
  integer :: i

  local_sum = 0.0_real64
  do i = 1, 100
    local_sum = local_sum + real(i * this_image(), real64)
  end do

  global_sum = local_sum
  call co_sum(global_sum, result_image=1)
  if (this_image() == 1) print *, 'Total:', global_sum

  ! other collectives: co_min, co_max, co_broadcast
  call co_broadcast(global_sum, source_image=1)
end program

Coarray 파생 타입

파생 타입은 coarray 가능 — 모든 구성 요소가 이미지별로 복제. p[k]%field로 원격 구성 요소 접근. coarray의 allocatable 구성 요소는 Fortran 2018+ 필요(일부 컴파일러는 미지원 가능). 입자 배열의 경우 type(particle), allocatable :: particles(:)[:] 사용.

fortran
program coarray_types
  implicit none
  type :: particle
    real :: x, y, z
    real :: mass
  end type
  type(particle) :: p[*]
  integer :: me

  me = this_image()
  p%mass = real(me)
  p%x = real(me) * 0.5

  sync all
  ! access component of remote coarray
  if (me == 1) print *, 'Image 2 mass:', p[2]%mass
end program

Allocatable coarray와 팀

Allocatable coarray는 [*] 접미사로 모든 이미지에서 동시에 할당. 팀(F2018)은 이미지를 독립적인 그룹으로 분할 — 각각 자체 this_image/num_images 보유. form team으로 팀 생성; change team으로 범위 진입. 계층적 병렬 처리에 유용. 컴파일러 지원은 다양(ifort, gfortran 9+).

fortran
program alloc_coarrays
  use iso_fortran_env, only: team_type
  implicit none
  integer, allocatable :: data(:)[:]
  type(team_type) :: odd_team, even_team
  integer :: me

  me = this_image()
  allocate(data(100)[*])  ! allocate on all images

  ! F2018 teams — split images into groups
  form team(merge(1, 2, mod(me, 2) == 1), odd_team, even_team)
  if (mod(me, 2) == 1) then
    change team(odd_team)
      ! this_image() and num_images() now refer to team
      data(this_image()) = me
    end team
  end if

  deallocate(data)
end program
11

C와의 상호 운용성

ISO_C_BINDING 기본

iso_c_binding은 C 호환 kind 제공(c_int, c_double, c_char 등). bind(C, name='...')는 특정 심볼 이름으로 Fortran을 C에 노출. C 문자열은 c_null_char 종결자 필요. 인터페이스 블록은 C 함수 시그니처 선언. C와 Fortran을 별도로 컴파일, 함께 링크.

fortran
program c_interop
  use iso_c_binding, only: c_int, c_double, c_char, c_null_char
  implicit none
  interface
    subroutine c_print(msg) bind(C, name='c_print')
      import :: c_char
      character(kind=c_char), dimension(*) :: msg
    end subroutine
  end interface

  call c_print('Hello from Fortran' // c_null_char)
end program

! Corresponding C:
!   void c_print(const char* msg) { printf("%s\n", msg); }

C에 배열 전달

c_loc(C 포인터 획득)와 c_ptr 타입으로 배열 전달. 스칼라 C 인수에는 'value' 속성 사용(참조가 아닌 값으로 전달). Fortran 배열은 열 우선; C는 행 우선 — 2D 배열 전치 또는 관례 문서화. c_f_pointer는 C 포인터를 Fortran 포인터로 변환.

fortran
program array_interop
  use iso_c_binding, only: c_double, c_int, c_loc, c_f_pointer
  implicit none
  real(c_double), target :: arr(10)
  integer(c_int) :: n
  type(c_ptr) :: ptr

  arr = [(real(i), i=1,10)]
  ptr = c_loc(arr(1))  ! get C pointer

  ! call C function: void process(double* arr, int n);
  call process_c(ptr, size(arr, kind=c_int))

  interface
    subroutine process_c(arr, n) bind(C, name='process')
      import :: c_double, c_int, c_ptr
      type(c_ptr), value :: arr
      integer(c_int), value :: n
    end subroutine
  end interface
end program

C 상호 운용 가능 타입

bind(C)가 있는 타입은 C 호환 메모리 레이아웃 보유 — C에 구조체 전달에 필수. C 호환 kind만 허용(기본 real/integer 없음). allocatable/pointer 구성 요소 없음. 고정 길이 char 배열은 C 문자열 에뮬레이션. 순서 중요 — bind(C) 없이 Fortran은 구성 요소 재정렬 가능.

fortran
module data_types
  use iso_c_binding, only: c_double, c_int, c_char
  implicit none

  type, bind(C) :: point
    real(c_double) :: x, y
    integer(c_int) :: id
  end type

  type, bind(C) :: string_holder
    character(kind=c_char, len=1) :: name(64)
  end type
end module

! C equivalent:
!   struct point { double x, y; int id; };
!   struct string_holder { char name[64]; };

C에서 Fortran 호출

bind(C, name='...')가 있는 서브루틴은 해당 이름으로 C에서 호출 가능. C가 값으로 전달하는 스칼라에는 'value' 사용; 배열은 참조로 전달(value 없음). 함수 결과는 C 호환 스칼라여야 함. 컴파일러 이름 망글링(밑줄, 대소문자 변경)을 피하기 위해 C 바인딩 이름 사용.

fortran
! Fortran:
module fmod
  use iso_c_binding, only: c_double
  implicit none
contains
  subroutine square_array(arr, n) bind(C, name='square_array')
    integer, value :: n
    real(c_double), intent(inout) :: arr(n)
    arr = arr ** 2
  end subroutine
end module

! C caller:
!   extern void square_array(double* arr, int n);
!   double data[5] = {1, 2, 3, 4, 5};
!   square_array(data, 5);

C 함수 포인터와 콜백

abstract interface는 C 함수 시그니처 선언. procedure(iface)는 일치하는 함수를 인수로 받음. C 함수 포인터를 직접 전달. c_funloc은 Fortran 프로시저의 C 주소 획득; c_f_procpointer는 c_funptr을 Fortran 프로시저로 변환. qsort 스타일 콜백에 유용.

fortran
module callbacks
  use iso_c_binding, only: c_funloc, c_funptr, c_int
  implicit none

  abstract interface
    function comparator(a, b) bind(C)
      import :: c_int
      integer(c_int), value :: a, b
      integer(c_int) :: comparator
    end function
  end interface

contains
  subroutine sort_with_c(arr, n, cmp) bind(C)
    integer(c_int), value :: n
    integer(c_int), intent(inout) :: arr(n)
    procedure(comparator) :: cmp
    ! ... use cmp(a, b) to compare ...
  end subroutine
end module

! C side:
!   int descending(int a, int b) { return b - a; }
!   sort_with_c(arr, n, descending);
12

객체 지향 (extends/final)

타입 확장 (상속)

타입 확장 = 상속. 'extends(parent)'는 하위 클래스 선언. 'class(T)'는 다형적(T 또는 모든 확장 받음); 'type(T)'는 정확함. 'abstract' + 'deferred' = 추상 메서드(반드시 override해야 함). 'contains'는 타입 바인딩 프로시저 도입. 같은 이름으로 프로시저 재선언하여 override.

fortran
module shapes
  implicit none
  type, abstract :: shape
    real :: x = 0, y = 0
  contains
    procedure(area_iface), deferred :: area
    procedure :: move => shape_move
  end type

  abstract interface
    function area_iface(this) result(a)
      import :: shape
      class(shape), intent(in) :: this
      real :: a
    end function
  end interface

  type, extends(shape) :: circle
    real :: radius
  contains
    procedure :: area => circle_area
  end type

contains
  subroutine shape_move(this, dx, dy)
    class(shape), intent(inout) :: this
    real, intent(in) :: dx, dy
    this%x = this%x + dx
    this%y = this%y + dy
  end subroutine

  function circle_area(this) result(a)
    class(circle), intent(in) :: this
    real :: a
    a = 3.14159 * this%radius ** 2
  end function
end module

다형성과 SELECT TYPE

select type은 다형적 변수에 대해 런타임 타입 판별. 'type is (T)'는 정확한 타입 매치; 'class is (T)'는 T와 확장 매치. 블록 내에서 변수는 매치된 타입으로 취급(특정 구성 요소 접근). 다형적 배열은 class(shape)를 통해 혼합 타입 보유 — 하지만 할당은 요소별여야 함.

fortran
module polymorph
  use shapes, only: shape, circle, rectangle
  implicit none
contains
  subroutine describe(s)
    class(shape), intent(in) :: s
    select type(s)
    type is (circle)
      print *, 'Circle radius:', s%radius
    type is (rectangle)
      print *, 'Rectangle:', s%width, 'x', s%height
    class is (shape)
      print *, 'Some shape at:', s%x, s%y
    class default
      print *, 'Unknown type'
    end select
  end subroutine

  subroutine process_all(shapes)
    class(shape), intent(in) :: shapes(:)
    integer :: i
    do i = 1, size(shapes)
      call describe(shapes(i))
      print *, 'Area:', shapes(i)%area()
    end do
  end subroutine
end module

Finalizer와 소멸자

final 프로시저는 변수가 범위를 벗어날 때 자동 실행 — C++ 소멸자처럼. 'final :: name'으로 정의. TYPE(class 아님)을 취하는 서브루틴이어야 함 — 다형성 없음. 하나의 타입이 여러 finalizer 가능(순위별 오버로드). 파일 닫기, 메모리 해제, 리소스 해제에 사용. 실패/발생 불가.

fortran
module resources
  implicit none
  type :: file_handle
    integer :: unit = -1
  contains
    final :: close_file
    procedure :: open
  end type
contains
  subroutine open(this, filename)
    class(file_handle), intent(inout) :: this
    character(*), intent(in) :: filename
    open(newunit=this%unit, file=filename, status='old')
  end subroutine

  subroutine close_file(this)
    type(file_handle), intent(inout) :: this
    if (this%unit /= -1) then
      close(this%unit)
      this%unit = -1
    end if
  end subroutine
end module

program demo
  use resources
  type(file_handle) :: f
  call f%open('data.txt')
  ! ... use file ...
end program  ! f goes out of scope -> close_file called automatically

생성자와 할당

타입 이름과 함께 제네릭 인터페이스는 사용자 정의 생성자로 작동 — 기본 구조 생성자 오버로드. 여러 프로시저가 다른 인수 세트 허용. 기본 생성자(point(x=..., y=...))는 override하지 않는 한 여전히 사용 가능. 다형적 할당: allocate(circle :: shape_var)는 class(shape) 변수에 circle 생성.

fortran
module points
  implicit none
  type :: point
    real :: x, y
  contains
    procedure :: norm
  end type
  interface point
    procedure new_point
    procedure new_point_polar
  end interface
contains
  function new_point(x, y) result(p)
    real, intent(in) :: x, y
    type(point) :: p
    p%x = x
    p%y = y
  end function

  function new_point_polar(r, theta) result(p)
    real, intent(in) :: r, theta
    type(point) :: p
    p%x = r * cos(theta)
    p%y = r * sin(theta)
  end function

  function norm(this) result(n)
    class(point), intent(in) :: this
    real :: n
    n = sqrt(this%x**2 + this%y**2)
  end function
end module

program use_points
  use points
  type(point) :: a, b
  a = point(1.0, 2.0)         ! cartesian
  b = point(3.0, 0.5)         ! polar (same name, different args)
  print *, a%norm(), b%norm()
end program

추상 타입과 템플릿

추상 타입은 인스턴스화 불가 — 확장만. 지연 프로시저는 구체적 하위 클래스에서 override해야 함. class(*)는 무제한 다형적 — 모든 타입 보유(복구하려면 select type 사용). 이 패턴은 추상 기반 클래스와 인터페이스 구현. 구체적 컨테이너(리스트, 스택, 큐)는 지연 프로시저를 확장하고 구현.

fortran
module container
  implicit none
  type, abstract :: container
  contains
    procedure(add_iface), deferred :: add
    procedure(size_iface), deferred :: size
    procedure :: is_empty => container_is_empty
  end type

  abstract interface
    subroutine add_iface(this, item)
      import :: container
      class(container), intent(inout) :: this
      class(*), intent(in) :: item
    end subroutine
    function size_iface(this) result(n)
      import :: container
      class(container), intent(in) :: this
      integer :: n
    end function
  end interface
contains
  function container_is_empty(this) result(e)
    class(container), intent(in) :: this
    logical :: e
    e = (this%size() == 0)
  end function
end module

! Concrete subclass implements add/size
! class(*) allows storing any type (unlimited polymorphic)
13

매개변수화된 파생 타입

기본 PDT 선언

매개변수화된 파생 타입(PDT, F2003)은 C++ 템플릿과 같음. 'kind' 매개변수는 컴파일 타임(인스턴스별 고정); 'len' 매개변수는 런타임(':'로 지연 가능). allocate(type(params) :: var)로 지연 길이 타입 할당. PDT는 전처리기 트릭 없이 타입 안전 제네릭 컨테이너 활성화.

fortran
module pdt_types
  implicit none
  type :: matrix(k, n, m)
    integer, kind :: k = kind(1.0)
    integer, len :: n, m
    real(k) :: data(n, m)
  end type

  ! kind parameters: compile-time (like template params)
  ! len parameters: runtime (like allocatable dimensions)
end module

program use_pdt
  use pdt_types
  implicit none
  type(matrix(kind(1.0), 3, 3)) :: small  ! fixed 3x3
  type(matrix(kind(1.d0), :, :)), allocatable :: big  ! deferred

  small%data = 0.0
  allocate(matrix(kind(1.d0), 100, 100) :: big)
  big%data = 0.d0
  deallocate(big)
end program

kind 매개변수가 있는 PDT

Kind 매개변수화 타입은 여러 정밀도에서 작동하는 하나의 타입 작성 허용. 함수 반환 타입은 this%k 사용하여 일치. 프로시저의 class(array_t(k=*)) 구문에 유의 — kind 매개변수 타입에 필수. 컴파일러 지원: gfortran 9+, ifort. 단정밀도/배정밀도/쿼드 정밀도를 지원하는 라이브러리에 유용.

fortran
module typed_array
  implicit none
  type :: array_t(k)
    integer, kind :: k = kind(1.0)
    real(k), allocatable :: data(:)
  contains
    procedure :: sum_values
  end type
contains
  function sum_values(this) result(s)
    class(array_t(k=*)), intent(in) :: this
    real(this%k) :: s
    s = sum(this%data)
  end function
end module

program test
  use typed_array
  type(array_t(kind(1.0)))  :: float_arr
  type(array_t(kind(1.d0))) :: double_arr

  allocate(float_arr%data(10))
  allocate(double_arr%data(10))
  float_arr%data = 1.0
  double_arr%data = 1.d0

  print *, float_arr%sum_values()
  print *, double_arr%sum_values()
end program

길이 매개변수가 있는 PDT

길이 매개변수화 타입은 크기를 타입 매개변수로 전달. fstr(5)와 fstr(6)은 다른 타입. 프로시저는 fstr(*)를 사용하여 모든 길이 허용. 결과 타입은 입력 길이에 의존 가능(c는 길이 a%n + b%n). 고정 크기 버퍼, 정적으로 크기가 지정된 배열에 유용. 제한된 컴파일러 지원 — 철저히 테스트.

fortran
module fixed_string
  implicit none
  type :: fstr(n)
    integer, len :: n
    character(len=n) :: str
  end type
contains
  function concat(a, b) result(c)
    type(fstr(*)), intent(in) :: a, b
    type(fstr(a%n + b%n)) :: c
    c%str = a%str // b%str
  end function
end module

program test
  use fixed_string
  type(fstr(5))  :: a = fstr(5)('Hello')
  type(fstr(6))  :: b = fstr(6)(' World')
  type(fstr(11)) :: c

  c = concat(a, b)
  print *, c%str
end program

PDT allocatable 배열

PDT는 allocatable 구성 요소 포함 가능. kind 매개변수는 구성 요소 타입으로 전파(real(this%k)). move_alloc은 효율적으로 할당 전송(복사 없음). 프로시저에서 class(stack(k=*))를 사용하여 모든 kind 허용. allocatable 구성 요소가 있는 PDT는 제네릭 타이핑과 동적 크기 지정 결합.

fortran
module stack_t
  implicit none
  type :: stack(k)
    integer, kind :: k = kind(1.0)
    real(k), allocatable :: data(:)
    integer :: top = 0
  contains
    procedure :: push
    procedure :: pop
    procedure :: is_empty
  end type
contains
  subroutine push(this, val)
    class(stack(k=*)), intent(inout) :: this
    real(this%k), intent(in) :: val
    if (this%top == size(this%data)) then
      block
        real(this%k), allocatable :: tmp(:)
        tmp = [this%data, val]
        call move_alloc(tmp, this%data)
      end block
    else
      this%top = this%top + 1
      this%data(this%top) = val
    end if
  end subroutine
end module

PDT 제한 사항과 해결 방법

PDT 지원은 다양 — kind 매개변수는 널리 지원; 길이 매개변수는 덜 지원. 런타임 크기 지정에는 len 매개변수보다 allocatable 구성 요소 선호. PDT 배열(type(matrix(4,4)) :: arr(10))은 모든 컴파일러에서 작동하지 않을 수 있음. 대상 컴파일러에서 테스트. 최대 이식성을 위해 전처리기(#define)나 제네릭 인터페이스 사용.

fortran
! Limitations of PDTs:
! - Not all compilers fully support len parameters
! - I/O of PDTs may not work as expected
! - Some F2003 features (e.g., PDT arrays) have spotty support

! Workaround: use allocatable components instead of len params
module alt_matrix
  implicit none
  type :: matrix_alt(k)
    integer, kind :: k = kind(1.0)
    real(k), allocatable :: data(:,:)
  end type
contains
  function make_matrix(k_, n, m) result(mat)
    integer, intent(in) :: n, m
    ! kind must be compile-time constant — can't pass dynamically
    ! workaround: separate constructors per kind
  end function
end module

! Best practice: use kind params (well-supported), avoid len params
! Use allocatable components for runtime sizing instead
14

서브모듈

기본 서브모듈 구조

서브모듈(F2008)은 인터페이스와 구현을 분리. 모듈은 인터페이스 선언; 서브모듈은 본체 제공. 서브모듈 본체의 변경은 종속 항목의 재컴파일을 유발하지 않음 — 인터페이스 변경만. 구현에 'module procedure' 사용. 비용이 많이 드는 컴파일이 있는 대규모 라이브러리에 좋음.

fortran
! parent_module.f90
module math_lib
  implicit none
  interface
    module function integrate(f, a, b, n) result(s)
      abstract interface
        real function func_t(x)
          real, intent(in) :: x
        end function
      end interface
      procedure(func_t) :: f
      real, intent(in) :: a, b
      integer, intent(in) :: n
      real :: s
    end function
  end interface
end module

! sub_math.f90
submodule(math_lib) math_impl
  implicit none
contains
  module procedure integrate
    integer :: i
    real :: dx, x
    dx = (b - a) / n
    s = 0.5 * (f(a) + f(b))
    do i = 1, n-1
      x = a + i * dx
      s = s + f(x)
    end do
    s = s * dx
  end procedure
end submodule

모듈당 여러 서브모듈

모듈은 여러 서브모듈을 가질 수 있음 — 구현을 파일 간 분할. 각 서브모듈은 'submodule(parent) name'으로 시작. 같은 부모 모듈의 프로시저는 서로 호출 가능. 점진적 컴파일 활성화: 하나의 서브모듈 편집, 그것만 재컴파일하고 링크. 매우 큰 모듈에 유용.

fortran
! big_lib.f90
module big_lib
  implicit none
  interface
    module subroutine sort(arr)
      real, intent(inout) :: arr(:)
    end subroutine
    module function mean(arr) result(m)
      real, intent(in) :: arr(:)
      real :: m
    end function
    module function stddev(arr) result(s)
      real, intent(in) :: arr(:)
      real :: s
    end function
  end interface
end module

! sub_sort.f90 — one submodule
submodule(big_lib) sort_impl
contains
  module procedure sort
    ! quicksort implementation
  end procedure
end submodule

! sub_stats.f90 — another submodule
submodule(big_lib) stats_impl
contains
  module procedure mean
    m = sum(arr) / size(arr)
  end procedure
  module procedure stddev
    block
      real :: mu
      mu = mean(arr)  ! can call sibling module procedures
      s = sqrt(sum((arr - mu)**2) / (size(arr) - 1))
    end block
  end procedure
end submodule

서브모듈 전용 내부 프로시저

서브모듈은 부모 모듈의 인터페이스를 통해 보이지 않는 private 헬퍼 프로시저 포함 가능. 이것은 구현 세부 정보를 숨기면서 함께 배치. 'module procedure' 구현만 부모를 통해 접근 가능. 헬퍼는 내부 유지 — 모듈의 contains 블록에 모든 것을 넣는 것보다 더 나은 캡슐화.

fortran
module sparse_ops
  implicit none
  interface
    module function sparse_multiply(a, b) result(c)
      ! ... sparse matrix types ...
      real, allocatable :: c(:,:)
    end function
  end interface
end module

submodule(sparse_ops) sparse_impl
  implicit none
contains
  module procedure sparse_multiply
    ! call helper — invisible to module users
    call validate_dimensions(a, b)
    c = multiply_kernel(a, b)
  end procedure

  ! private helper — not exposed via parent module
  subroutine validate_dimensions(a, b)
    ! ...
  end subroutine

  function multiply_kernel(a, b) result(c)
    ! ...
  end function
end submodule

모듈 변수 상속

서브모듈은 호스트 연관을 통해 부모 모듈의 변수와 타입에 접근 가능. 모듈 수준 상태를 읽고 수정 가능. 구현 동작에 영향을 미치는 설정에 유용. 모듈 변수의 변경은 여전히 서브모듈(및 종속 항목) 재컴파일 필요.

fortran
module config
  implicit none
  integer :: verbosity = 0
  interface
    module subroutine log_message(msg)
      character(*), intent(in) :: msg
    end subroutine
  end interface
end module

submodule(config) config_impl
  implicit none
  ! submodule can access parent module's variables
contains
  module procedure log_message
    if (verbosity > 0) then
      print *, '[LOG]', msg
    end if
  end procedure
end submodule

! Usage:
!   use config
!   verbosity = 1
!   call log_message('Starting up')

서브모듈 컴파일 이점

주요 이점: 서브모듈의 구현 변경은 부모 모듈을 'use'하는 코드의 재컴파일을 유발하지 않음. 서브모듈 자체만 재컴파일. 이것은 대규모 Fortran 프로젝트의 점진적 빌드를 극적으로 가속. 자주 변경되는 구현을 서브모듈로 재구성; 안정적인 인터페이스는 부모에 유지.

fortran
! Without submodules:
!   module big_mod
!     ... 5000 lines of implementation ...
!   end module
!   ! Any change -> recompile big_mod + all users

! With submodules:
!   module big_mod
!     ! just interfaces (~200 lines)
!   end module
!   submodule(big_mod) impl
!     ! 5000 lines of implementation
!   end submodule
!   ! Change impl body -> recompile only submodule
!   ! Change interface -> recompile everything (unavoidable)

! Build system example (Makefile):
!   big_mod.o: big_mod.f90
!   impl.o: impl.f90 big_mod.o
!   user.o: user.f90 big_mod.o  ! NOT impl.o
!   app: user.o impl.o big_mod.o
!   	$(FC) -o app user.o impl.o big_mod.o
15

IEEE 부동소수점

IEEE 예외와 플래그

ieee_exceptions는 IEEE 예외 플래그(오버플로우, 언더플로우, 0으로 나누기, 무효, 부정확)에 접근 제공. ieee_get_flag로 읽기; ieee_set_flag로 지우기. ieee_set_halting_mode는 예외가 프로그램을 중단할지 제어. ieee_arithmetic은 ieee_is_nan, ieee_is_finite, ieee_is_negative 등 제공. 중요한 계산 후 항상 플래그 확인.

fortran
program ieee_exceptions
  use ieee_exceptions
  use ieee_arithmetic, only: ieee_is_nan, ieee_is_finite
  implicit none
  real :: x, y
  logical :: overflow_flag

  call ieee_set_halting_mode(ieee_overflow, .false.)
  x = huge(x) * 10  ! overflow -> Inf, no halt

  call ieee_get_flag(ieee_overflow, overflow_flag)
  print *, 'Overflow occurred:', overflow_flag

  y = 0.0 / 0.0  ! NaN
  print *, 'x is finite:', ieee_is_finite(x)
  print *, 'y is NaN:', ieee_is_nan(y)

  call ieee_set_flag(ieee_all, .false.)  ! clear all flags
end program

NaN과 Inf 처리

Inf와 NaN은 IEEE 특수 값. NaN != NaN(테스트에는 ieee_is_nan 사용). Inf는 산술을 통해 전파. NaN을 생성하는 연산: 0/0, Inf-Inf, 0*Inf, sqrt(-1). 이들에서 중단하려면 -ffpe-trap=invalid,zero,overflow로 컴파일(디버깅). 프로덕션 코드는 위험한 연산 후 NaN 확인해야 함.

fortran
program nan_inf
  use ieee_arithmetic, only: ieee_value, ieee_nan, &
                             ieee_positive_inf, ieee_negative_inf
  implicit none
  real :: pos_inf, neg_inf, nan_val

  pos_inf = ieee_value(0.0, ieee_positive_inf)
  neg_inf = ieee_value(0.0, ieee_negative_inf)
  nan_val = ieee_value(0.0, ieee_nan)

  print *, 'Inf - Inf =', pos_inf - pos_inf  ! NaN
  print *, '0 * Inf =', 0.0 * pos_inf         ! NaN
  print *, 'Inf > 1e30:', pos_inf > 1e30       ! T
  print *, 'NaN == NaN:', nan_val == nan_val   ! F (always!)

  ! generate via arithmetic
  pos_inf = 1.0 / 0.0  ! may halt without -fno-trapping-math
end program

반올림 모드

IEEE는 4가지 반올림 모드 지원: 최근접(기본값), 내림, 올림, 0으로. ieee_set_rounding_mode로 런타임에 변경. 구간 산술(올림/내림으로 상한/하한 계산)에 유용. 변경될 때까지 모든 후속 FP 연산에 영향. 완료 후 최근접으로 복원. 일부 컴파일러는 최근접 가정으로 최적화 — 주의해서 사용.

fortran
program rounding_modes
  use ieee_arithmetic, only: ieee_set_rounding_mode, &
                             ieee_nearest, ieee_down, ieee_up, ieee_to_zero
  implicit none
  real :: x, y, result

  x = 1.0 / 3.0
  print *, 'Nearest:', x

  call ieee_set_rounding_mode(ieee_down)
  result = 1.0 / 3.0
  print *, 'Down:', result

  call ieee_set_rounding_mode(ieee_up)
  result = 1.0 / 3.0
  print *, 'Up:', result

  call ieee_set_rounding_mode(ieee_to_zero)
  result = 1.0 / 3.0
  print *, 'To zero:', result

  call ieee_set_rounding_mode(ieee_nearest)  ! restore default
end program

IEEE 기능 조회

ieee_features와 ieee_arithmetic 조회 함수로 컴파일러/플랫폼이 지원하는 IEEE 기능 확인 가능. ieee_support_nan, ieee_support_inf, ieee_support_rounding, ieee_support_datatype 등. 이식 가능한 코드에 유용 — 비-IEEE 시스템에서 우아하게 저하. 대부분의 현대 시스템은 모든 기능 지원.

fortran
program ieee_inquiry
  use ieee_features
  use ieee_arithmetic, only: ieee_support_nan, ieee_support_inf, &
                             ieee_support_rounding, ieee_nearest
  implicit none

  if (ieee_support_nan(1.0)) print *, 'NaN supported'
  if (ieee_support_inf(1.0)) print *, 'Inf supported'
  if (ieee_support_rounding(ieee_nearest, 1.0)) &
    print *, 'Nearest rounding supported'

  ! Check halting mode
  block
    use ieee_exceptions
    logical :: halting
    call ieee_get_halting_mode(ieee_overflow, halting)
    print *, 'Overflow halts:', halting
  end block
end program

FP 환경 제어

섹션별 FP 동작 제어: 위험한 코드에 대해 중단 비활성화, 후에 플래그 확인, 복원. ieee_all은 모든 예외 매치. 패턴: 플래그 지우기, 계산 실행, 플래그 확인, 오류 처리. 프로덕션의 경우 플래그 기반 감지보다 명시적 확인(ieee_is_nan) 선호 — 플래그는 관련 없는 코드에 의해 설정될 수 있음.

fortran
program fp_env
  use ieee_arithmetic
  use ieee_exceptions
  implicit none
  real :: a, b, c

  ! Save/restore FP state
  block
    use iso_fortran_env, only: real32
    real(real32) :: saved_state
    ! (no portable save/restore in standard — use compiler intrinsics)
  end block

  ! Disable halting for a risky section
  call ieee_set_halting_mode(ieee_all, .false.)
  call ieee_set_flag(ieee_all, .false.)

  a = sqrt(-1.0)  ! NaN, no halt
  b = 1.0 / 0.0   ! Inf, no halt

  if (ieee_is_nan(a)) then
    print *, 'Got NaN, using fallback'
    a = 0.0
  end if

  ! Re-enable halting
  call ieee_set_halting_mode(ieee_divide_by_zero, .true.)
  call ieee_set_halting_mode(ieee_invalid, .true.)
end program
16

성능 & 최적화

배열 순서와 연속성

Fortran 배열은 열 우선: a(i,j)와 a(i+1,j)는 메모리에서 인접. 루프 순서 중요: 가장 안쪽 루프는 첫 번째 인덱스 반복해야 함. 잘못된 순서는 캐시 미스 발생 — 10배 이상 느려짐. 배열 구문(sum, matmul)은 컴파일러가 최적화하게 함. 최상의 성능을 위해 포인터가 아닌 연속 배열 사용.

fortran
program array_perf
  implicit none
  real, allocatable :: a(:,:), b(:,:)
  integer :: i, j, n
  real :: s

  n = 1000
  allocate(a(n,n), b(n,n))

  ! GOOD: column-major access (Fortran is column-major)
  do j = 1, n
    do i = 1, n
      s = s + a(i,j)
    end do
  end do

  ! BAD: row-major access (cache misses)
  do i = 1, n
    do j = 1, n
      s = s + a(i,j)
    end do
  end do

  ! BEST: array syntax (compiler optimizes)
  s = sum(a)
end program

순수와 기본적 프로시저

pure 프로시저는 부작용 없음 — 컴파일러가 호출 최적화, 병렬화, 재정렬 가능. elemental 프로시저는 스칼라와 배열 모두에서 작동(자동 벡터화). pure elemental은 둘 다 결합. 수학 함수에 사용. 제한: I/O 없음, 전역 수정 없음, 모든 입력에 intent(in). 일반 프로시저보다 더 나은 최적화 활성화.

fortran
module math_ops
  implicit none
contains
  ! pure: no side effects, enables optimization
  pure function square(x) result(y)
    real, intent(in) :: x
    real :: y
    y = x * x
  end function

  ! elemental: works on scalars and arrays element-wise
  pure elemental function clamp(x, lo, hi) result(y)
    real, intent(in) :: x, lo, hi
    real :: y
    y = max(lo, min(x, hi))
  end function
end module

program use_math
  use math_ops
  implicit none
  real :: arr(100), scalar

  arr = clamp(arr, 0.0, 1.0)  ! applies to each element
  scalar = clamp(1.5, 0.0, 1.0)  ! also works on scalar
  arr = square(arr)  ! elemental too (if declared)
end program

컴파일러 최적화 플래그

프로덕션을 위해 -O2 -march=native로 시작. -O3는 도움되거나 해로울 수 있음 — 벤치마크. -Ofast는 IEEE 준수 위반(NaN/Inf가 중요하면 사용 금지). -flto는 크로스 파일 인라이닝 활성화(느린 컴파일, 빠른 실행). 디버그 빌드에 -fcheck=all과 -ffpe-trap로 오류 잡기. 프로파일 기반 최적화(-fprofile-use)는 5-15% 가속 제공.

fortran
! gfortran optimization flags:
!   -O0   no optimization (debug)
!   -O1   basic optimization
!   -O2   standard optimization (recommended)
!   -O3   aggressive (may increase code size)
!   -Ofast  -O3 + -ffast-math (breaks IEEE compliance)
!   -march=native  use CPU's full instruction set
!   -flto  link-time optimization (cross-file inlining)
!   -fopenmp  enable OpenMP pragmas
!   -fprofile-generate/use  profile-guided optimization

! Example Makefile:
!   FC = gfortran
!   FFLAGS = -O2 -march=native -Wall -fcheck=all
!   FFLAGS_RELEASE = -O3 -march=native -flto -fno-trapping-math
!   FFLAGS_DEBUG = -O0 -g -fcheck=all -fbacktrace -ffpe-trap=invalid,zero

! ifort equivalents:
!   -O2, -O3, -xHost (= -march=native), -ipo (= -flto)

OpenMP 병렬 처리

OpenMP는 지시문으로 병렬 처리 추가(!$omp). 'parallel do'는 다음 루프를 병렬화. 'reduction(+:s)'는 합계를 안전하게 처리. -fopenmp(gfortran) 또는 -qopenmp(ifort)로 컴파일. OMP_NUM_THREADS 환경 변수로 스레드 수 설정. 독립적인 반복이 있는 CPU 바운드 루프에 가장 적합. 거짓 공유와 부하 불균형 주의.

fortran
program openmp_demo
  use omp_lib
  implicit none
  real, allocatable :: a(:), b(:), c(:)
  integer :: i, n, nthreads

  n = 10000000
  allocate(a(n), b(n), c(n))
  a = 1.0; b = 2.0

  !$omp parallel
  if (omp_get_thread_num() == 0) then
    nthreads = omp_get_num_threads()
    print *, 'Threads:', nthreads
  end if
  !$omp end parallel

  !$omp parallel do
  do i = 1, n
    c(i) = a(i) + b(i)
  end do
  !$omp end parallel do

  ! reduction
  block
    real :: s
    s = 0.0
    !$omp parallel do reduction(+:s)
    do i = 1, n
      s = s + c(i)
    end do
    !$omp end parallel do
    print *, 'Sum:', s
  end block
end program

프로파일링과 핫스팟 감지

system_clock는 이식 가능한 타이밍 제공. 심각한 프로파일링의 경우 gprof(-pg로 컴파일), perf(Linux), 또는 Intel VTune 사용. 최적화 전에 프로파일 — 놀라움이 흔함. 핫스팟에 집중(시간의 80%를 차지하는 코드의 20%). 가장 안쪽 루프 먼저 최적화. 변경 전후에 항상 벤치마크 — 직감은 종종 틀림.

fortran
! Compile with profiling:
!   gfortran -pg -O2 prog.f90 -o prog
!   ./prog  (generates gmon.out)
!   gprof prog gmon.out > profile.txt

! Or use perf (Linux):
!   perf record ./prog
!   perf report

! Or gprofng (modern):
!   gprofng collect app ./prog
!   gprofng analyze

! Manual timing:
program timing
  use iso_fortran_env, only: real64, int64
  implicit none
  integer(int64) :: start, finish, rate
  real(real64) :: elapsed
  real, allocatable :: a(:)
  integer :: i

  allocate(a(10000000))
  call system_clock(start, rate)
  do i = 1, 100
    a = a + 1.0
  end do
  call system_clock(finish)
  elapsed = real(finish - start, real64) / real(rate, real64)
  print *, 'Elapsed:', elapsed, 'sec'
end program
17

혼합 언어 프로그래밍

Fortran에서 C 라이브러리 호출

C 라이브러리 함수를 Fortran 인터페이스 모듈로 래핑. bind(C, name='...')로 정확한 C 심볼에 링크. 포인터를 c_ptr에 'value'로 전달. 함수 포인터 인수(qsort의 비교자처럼)에는 abstract interface 사용. 이것은 libc, BLAS, 시스템 호출을 포함한 모든 C 함수 호출 가능.

fortran
module c_glue
  use iso_c_binding
  implicit none
  interface
    function c_malloc(size) bind(C, name='malloc')
      import :: c_ptr, c_size_t
      integer(c_size_t), value :: size
      type(c_ptr) :: c_malloc
    end function

    subroutine c_free(ptr) bind(C, name='free')
      import :: c_ptr
      type(c_ptr), value :: ptr
    end subroutine

    subroutine c_qsort(base, nmemb, size, compar) bind(C, name='qsort')
      import :: c_ptr, c_size_t, c_int
      type(c_ptr), value :: base
      integer(c_size_t), value :: nmemb, size
      abstract interface
        function compar_fn(a, b) bind(C)
          import :: c_ptr
          type(c_ptr), value :: a, b
          integer(c_int) :: compar_fn
        end function
      end interface
      procedure(compar_fn) :: compar
    end subroutine
  end interface
end module

extern C를 통한 C++ 상호 운용

C++는 심볼을 이름 망글링하므로 C++ 함수를 extern "C" 블록으로 래핑. Fortran 인터페이스는 망글링되지 않은 이름에 바인딩. -lstdc++(gfortran)로 링크 또는 C++ 링커 사용. 이것은 Fortran에서 C++ 라이브러리(STL, Boost, Qt) 사용 가능. C++ 클래스의 경우 평면 C API 래퍼 작성, 그다음 Fortran에서 호출.

fortran
! C++ side (wrapper.cpp):
!   extern "C" {
!     void cpp_process(double* data, int n) {
!       std::vector<double> v(data, data+n);
!       std::sort(v.begin(), v.end());
!       std::copy(v.begin(), v.end(), data);
!     }
!   }

! Fortran side:
module cpp_glue
  use iso_c_binding
  implicit none
  interface
    subroutine cpp_process(data, n) bind(C, name='cpp_process')
      import :: c_double, c_int
      real(c_double), intent(inout) :: data(*)
      integer(c_int), value :: n
    end subroutine
  end interface
end module

program use_cpp
  use cpp_glue
  implicit none
  real(c_double) :: arr(10) = [5.d0, 3.d0, 8.d0, 1.d0, 9.d0, &
                                2.d0, 7.d0, 4.d0, 6.d0, 0.d0]
  call cpp_process(arr, size(arr, kind=c_int))
  print *, arr
end program

! Build: gfortran use_cpp.f90 wrapper.cpp -lstdc++ -o app

f2py를 통한 Python 상호 운용

f2py(numpy의 일부)는 Fortran용 Python 바인딩 자동 생성. intent 속성을 읽고 적절한 Python/numpy 인터페이스 생성. intent(out)은 반환값이 됨. 배열은 numpy 배열에 매핑(연속일 때 제로 카피). Python에서 호출되는 성능 중심 수치 코드에 좋음. F2003+ 기능에 대한 제한된 지원.

fortran
! Fortran module (myfuncs.f90):
module myfuncs
  implicit none
contains
  subroutine compute(arr, n, result)
    integer, intent(in) :: n
    real(8), intent(in) :: arr(n)
    real(8), intent(out) :: result
    result = sum(arr**2)
  end subroutine

  function fast_sin(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = sin(x)
  end function
end module

! Build Python extension:
!   f2py -c myfuncs.f90 -m myfuncs
!   (creates myfuncs.cpython-*.so)

! Python usage:
!   import numpy as np
!   import myfuncs
!   arr = np.array([1.0, 2.0, 3.0])
!   result = myfuncs.myfuncs.compute(arr)
!   y = myfuncs.myfuncs.fast_sin(1.5)

공유 라이브러리와 동적 로딩

런타임 로딩 플러그인에 dlopen/dlsym(POSIX) 또는 LoadLibrary/GetProcAddress(Windows) 사용. iso_c_binding 인터페이스로 래핑. c_f_procpointer로 c_ptr을 프로시저 포인터로 변환. 이것은 플러그인 아키텍처 활성화 — 런타임에 다른 구현 로드. 크로스 플랫폼: OS 특정 로더에 #ifdef 사용.

fortran
module dynamic_loader
  use iso_c_binding
  implicit none
  interface
    function dlopen(filename, flag) bind(C, name='dlopen')
      import :: c_ptr, c_char, c_int
      character(kind=c_char), dimension(*) :: filename
      integer(c_int), value :: flag
      type(c_ptr) :: dlopen
    end function

    function dlsym(handle, name) bind(C, name='dlsym')
      import :: c_ptr, c_char
      type(c_ptr), value :: handle
      character(kind=c_char), dimension(*) :: name
      type(c_ptr) :: dlsym
    end function

    function dlclose(handle) bind(C, name='dlclose')
      import :: c_ptr, c_int
      type(c_ptr), value :: handle
      integer(c_int) :: dlclose
    end function
  end interface
end module

program plugin
  use dynamic_loader
  implicit none
  type(c_ptr) :: lib, sym
  integer, parameter :: RTLD_NOW = 2

  lib = dlopen('./plugin.so' // c_null_char, RTLD_NOW)
  if (.not. c_associated(lib)) then
    print *, 'Failed to load library'
    stop 1
  end if

  sym = dlsym(lib, 'init' // c_null_char)
  ! call init via c_f_procpointer...
  print *, dlclose(lib)
end program

빌드 시스템 통합

CMake는 혼합 언어 빌드를 잘 처리. project()에 모든 언어 선언. 언어별 플래그 설정. 종속성 순서로 라이브러리 링크. CMake는 Fortran 모듈 종속성을 자동 추적(수동 .mod 순서 불필요). C++ 표준 라이브러리 링크에는 플랫폼별 올바른 플래그 사용. Fortran 런타임이 필요한 경우 Fortran을 메인 링커로 사용.

fortran
# CMakeLists.txt for mixed Fortran/C/C++ project:
cmake_minimum_required(VERSION 3.18)
project(mixed LANGUAGES Fortran C CXX)

set(CMAKE_Fortran_FLAGS "-O2 -fopenmp")
set(CMAKE_C_FLAGS "-O2")
set(CMAKE_CXX_FLAGS "-O2 -std=c++17")

add_library(fortran_lib STATIC mymod.f90)
add_library(c_lib STATIC helper.c)
add_library(cpp_lib STATIC wrapper.cpp)

# Mixed-language executable
add_executable(app main.f90)
target_link_libraries(app fortran_lib c_lib cpp_lib)

# Fortran needs C runtime
if(APPLE)
  target_link_libraries(app "-lc++")
else()
  target_link_libraries(app stdc++)
endif()

# Module dependency tracking (CMake handles automatically)
#   fortran_lib depends on its own .mod files
#   app depends on fortran_lib's .mod files
18

현대 I/O

Newunit과 안전한 파일 처리

newunit=은 유닛 번호 충돌 방지 — 컴파일러가 여유 번호 선택. open/read/write 후 항상 iostat 확인 — 0이 아닌 값은 오류 의미. iomsg는 사람이 읽을 수 있는 오류 메시지 제공. action= ('read', 'write', 'readwrite')는 우발적 오용 방지. status= ('old', 'new', 'replace', 'scratch', 'unknown')는 파일 생성 제어.

fortran
program safe_io
  implicit none
  integer :: u, ios
  character(256) :: msg

  ! newunit: compiler picks an unused unit number
  open(newunit=u, file='data.txt', status='old', action='read', &
       iostat=ios, iomsg=msg)
  if (ios /= 0) then
    print *, 'Open failed:', trim(msg)
    stop 1
  end if

  ! read with error handling
  read(u, *, iostat=ios, iomsg=msg) some_value
  if (ios /= 0) then
    print *, 'Read failed:', trim(msg)
  end if

  close(u)
contains
  integer function some_value()
    some_value = 0
  end function
end program

스트림 I/O (바이너리)

스트림 접근(F2003)은 C처럼 바이트 지향 I/O 제공 — 레코드 마커 없음. access='stream'로 활성화. pos=는 특정 바이트 오프셋에서 읽기/쓰기. 바이너리의 경우 form='unformatted'. 상호 운용 가능한 바이너리 파일에 좋음(C/Python에서 Fortran 작성 파일 읽기). 기본 순차 접근은 레코드 마커 사용(비이식성).

fortran
program stream_io
  implicit none
  integer :: u, i
  real, allocatable :: data(:)

  ! stream access = byte-oriented (like C fread/fwrite)
  open(newunit=u, file='data.bin', access='stream', &
       form='unformatted', status='replace')

  data = [(real(i), i=1,100)]
  write(u) data  ! binary, no record markers
  close(u)

  ! read back
  allocate(data(100))
  open(newunit=u, file='data.bin', access='stream', &
       form='unformatted', status='old', action='read')
  read(u) data
  close(u)

  ! position-based access
  open(newunit=u, file='data.bin', access='stream', &
       form='unformatted', status='old')
  read(u, pos=21) data(1)  ! read 5th real (4 bytes each)
  close(u)
end program

파생 타입 I/O

사용자 정의 파생 타입 I/O(F2003)는 타입이 읽히고/쓰이는 방식 제어. 특정 시그니처로 서브루틴 정의하고 'generic :: write(formatted)'로 바인딩. DT 형식 코드가 트리거. 사용자 정의 직렬화 활성화(CSV, JSON 유사, 바이너리). iotype는 'LISTDIRECTED', 'NAMELIST', 또는 서식 I/O용 'DT'.

fortran
module person_type
  implicit none
  type :: person
    character(20) :: name
    integer :: age
    real :: height
  end type
contains
  ! custom formatted I/O via DT format
  subroutine write_person(dtv, unit, iotype, v_list, iostat, iomsg)
    class(person), intent(in) :: dtv
    integer, intent(in) :: unit
    character(*), intent(in) :: iotype
    integer, intent(in) :: v_list(:)
    integer, intent(out) :: iostat
    character(*), intent(inout) :: iomsg
    write(unit, '(a,",",i0,",",f0.2)', iostat=iostat) &
      trim(dtv%name), dtv%age, dtv%height
  end subroutine
end module

program use_dtv
  use person_type
  implicit none
  type(person) :: p = person('Alice', 30, 5.7)

  ! DT format triggers custom writer
  print "(DT)", p  ! calls write_person
end program

Namelist I/O

Namelist는 변수 그룹에 대해 사람이 읽을 수 있고 이름 기반 I/O 제공. 형식: &group_name var=value, ... /. 읽기는 파일에 있는 변수만 업데이트 — 다른 것은 값 유지. 설정 파일에 좋음 — 사용자가 텍스트 편집, 파서 불필요. 제한: 일부 컴파일러에서 주석 없음, 제한된 타입 지원.

fortran
program namelist_demo
  implicit none
  integer :: n_iterations = 100
  real :: tolerance = 1.0e-6
  character(50) :: output_file = 'results.txt'
  logical :: verbose = .true.

  namelist /config/ n_iterations, tolerance, output_file, verbose

  ! write namelist
  open(newunit=u, file='config.nml', status='replace')
  write(u, nml=config)
  close(u)

  ! read namelist (only specified vars are updated)
  open(newunit=u, file='config.nml', status='old', action='read')
  read(u, nml=config)
  close(u)

  print *, 'Iterations:', n_iterations
end program

! config.nml format:
! &config
!   n_iterations = 500
!   tolerance = 1.0e-8
!   verbose = .false.
! /

비동기 I/O

비동기 I/O(F2003)는 I/O와 계산을 겹침. write(..., asynchronous='yes')는 비차단 작업 시작. wait(unit)는 완료까지 차단. 대규모 데이터셋에 유용 — 다음 청크 계산하면서 쓰기 시작. 컴파일러 지원은 다양. inquire(unit=u, pending=...)로 상태 확인. 파이프라인을 위해 이중 버퍼링과 짝지음.

fortran
program async_io
  implicit none
  integer :: u1, u2, ios
  real, allocatable :: a(:), b(:)

  allocate(a(1000000), b(1000000))
  a = 1.0; b = 2.0

  open(newunit=u1, file='a.bin', access='stream', form='unformatted', &
       asynchronous='yes')
  open(newunit=u2, file='b.bin', access='stream', form='unformatted', &
       asynchronous='yes')

  ! non-blocking writes
  write(u1, asynchronous='yes') a
  write(u2, asynchronous='yes') b

  ! do other work while I/O proceeds...

  ! wait for completion
  wait(u1)
  wait(u2)

  close(u1); close(u2)
end program
19

디버깅 & 프로파일링

컴파일 타임 디버깅 플래그

범위 검사를 위해 -fcheck=all 사용(오프바이원 오류 잡기). -ffpe-trap은 NaN/Inf/오버플로우에서 중단 — 수치 코드에 귀중. -finit-real=nan은 초기화되지 않은 변수를 가시적으로 만듦(NaN으로 전파). -fbacktrace는 크래시 시 스택 추적 출력. 이러한 플래그로 항상 디버그; 프로덕션 빌드에서 제거(코드 느려짐).

fortran
! gfortran debug build:
!   gfortran -O0 -g -fcheck=all -fbacktrace -ffpe-trap=invalid,zero,overflow \
!            -Wall -Wextra -Wpedantic -finit-real=nan prog.f90

! Flag explanations:
!   -O0          no optimization (easier debugging)
!   -g           debug symbols (for gdb)
!   -fcheck=all  bounds, pointer, recursion checks
!   -fbacktrace  print stack trace on error
!   -ffpe-trap   halt on FP exceptions (invalid, zero, overflow)
!   -finit-real=nan  initialize reals to NaN (catch uninitialized)
!   -finit-integer=-9999  initialize integers to sentinel
!   -Wall -Wextra  more warnings

! Runtime error message:
!   At line 42 of file prog.f90
!   Fortran runtime error: Array bound out of bounds for dimension 1

iostat로 오류 처리

iostat: 음수 = EOF, 0 = 성공, 양수 = 오류. iomsg는 세부 정보 제공. 먼저 문자열로 읽고 그다음 파싱 — I/O 오류와 파싱 오류 분리. 도움이 되는 오류 메시지를 위해 줄 번호 추적. 항상 명시적으로 오류 처리 — 조용한 실패는 디버그하기 어려움. 스크립트에 실패 신호를 위해 0이 아닌 코드로 stop 사용.

fortran
program robust_io
  implicit none
  integer :: u, ios, line_num
  character(256) :: msg, line
  real :: value

  open(newunit=u, file='data.txt', status='old', action='read', &
       iostat=ios, iomsg=msg)
  if (ios /= 0) call error_exit('Open: ' // trim(msg))

  line_num = 0
  do
    line_num = line_num + 1
    read(u, '(a)', iostat=ios) line
    if (ios < 0) exit  ! EOF
    if (ios > 0) then
      print *, 'Read error at line', line_num
      cycle
    end if

    read(line, *, iostat=ios, iomsg=msg) value
    if (ios /= 0) then
      print *, 'Parse error at line', line_num, ':', trim(msg)
      cycle
    end if
    ! process value
  end do
  close(u)
contains
  subroutine error_exit(m)
    character(*), intent(in) :: m
    print *, 'ERROR:', m
    stop 1
  end subroutine
end program

Fortran용 GDB

GDB는 Fortran 지원: 배열 슬라이스, 파생 타입 구성 요소, 모듈 프로시저(이름 형식: modname__procname). 최상의 디버깅 경험을 위해 -g -O0 사용. 'display'는 각 정지 시 변수 자동 출력 — 루프 감시에 유용. 'info locals'는 모든 지역 변수 표시. 모듈 변수의 경우 'print modname::varname' 사용.

fortran
! Compile: gfortran -g -O0 prog.f90 -o prog
! Start: gdb ./prog

! Common GDB commands for Fortran:
!   (gdb) break main           break at main
!   (gdb) break prog.f90:42     break at line 42
!   (gdb) break mymod__my_sub   break at subroutine (note double underscore)
!   (gdb) run                   start program
!   (gdb) next                  step over
!   (gdb) step                  step into
!   (gdb) print arr(5)          print array element
!   (gdb) print arr(1:10)       print array slice
!   (gdb) print mat(2,3)        print 2D array element
!   (gdb) print p%name          print derived type component
!   (gdb) display x             watch variable (auto-print on stop)
!   (gdb) backtrace             show call stack
!   (gdb) info locals           show all local variables
!   (gdb) continue              resume execution

gprof 프로파일링

gprof는 실행 중 프로그램 카운터 샘플링. 완전한 프로파일을 위해 모든 소스 파일을 -pg로 컴파일. 평면 프로파일은 시간이 소비되는 곳 표시; 호출 그래프는 호출 계층 표시. 높은 'self' 시간의 함수에 집중 — 그곳이 최적화가 도움되는 곳. 참고: -pg는 타이밍 변경 — 프로파일 같은 코드는 프로덕션에서 다르게 동작 가능.

fortran
! Compile with profiling:
!   gfortran -pg -O2 prog.f90 -o prog
! Run:
!   ./prog
!   (creates gmon.out)
! Analyze:
!   gprof prog gmon.out > profile.txt
!   gprof prog gmon.out | less

! profile.txt sections:
!   - Flat profile: time per function (self + cumulative)
!   - Call graph: who called whom, how many times
!   - Index: function cross-references

! Key columns:
!   %time  percentage of total time
!   cumulative  running total
!   self    seconds in function (excluding children)
!   calls   number of calls
!   self/call  average time per call (self)

! For multi-threaded: use gprofng (modern) or perf

성능 벤치마킹

적절하게 벤치마크: 먼저 워밍업(캐시 효과), 여러 반복 실행, 최소값 취하기(가장 적은 노이즈). count_rate가 있는 system_clock은 경과 시간 제공. 배열 연산의 경우 처리량(요소/초) 보고. 같은 머신에서 같은 플래그로 구현 비교. 주의: 컴파일러는 '사용하지 않은' 결과를 최적화 제거 가능 — 결과 사용(예: 합계 출력)으로 방지.

fortran
program benchmark
  use iso_fortran_env, only: real64, int64
  implicit none
  integer, parameter :: n = 1000000
  integer, parameter :: niter = 100
  real(real64), allocatable :: a(:), b(:), c(:)
  integer(int64) :: start, finish, rate
  real(real64) :: t_start, t_end, min_time
  integer :: i, iter

  allocate(a(n), b(n), c(n))
  a = 1.0_real64; b = 2.0_real64

  ! warmup (cache, JIT-like effects)
  do i = 1, n
    c(i) = a(i) + b(i)
  end do

  min_time = huge(min_time)
  call system_clock(count_rate=rate)
  do iter = 1, niter
    call system_clock(start)
    do i = 1, n
      c(i) = a(i) + b(i)
    end do
    call system_clock(finish)
    t_start = real(start, real64) / rate
    t_end = real(finish, real64) / rate
    min_time = min(min_time, t_end - t_start)
  end do

  print *, 'Best time:', min_time * 1e6, 'us'
  print *, 'Throughput:', real(n) / min_time / 1e9, 'G elem/s'
end program
20

현대 Fortran

자유 형식

현대 Fortran(90+)은 자유 형식 사용: 열 제한 없음. 주석은 !로 시작. 문장은 &로 여러 줄에 걸칠 수 있음. 타입 안전을 위해 implicit none 필수. 고정 형식 Fortran 77보다 훨씬 더 읽기 쉬움.

fortran
program modern
    implicit none
    integer :: i
    do i = 1, 10
        print *, "Value:", i
    end do
end program modern
! Free form: no column restrictions
! Comments start with !

모듈

모듈은 관련 프로시저와 데이터를 그룹화. use는 모듈 임포트. contains는 모듈 수준 선언을 프로시저에서 분리. 모듈은 명시적 인터페이스 제공, 타입 검사 활성화. 외부 프로시저보다 모듈 선호.

fortran
module math_utils
    implicit none
    contains
    function square(x) result(y)
        real, intent(in) :: x
        real :: y
        y = x * x
    end function square
end module math_utils

program test
    use math_utils
    print *, square(3.0)  ! 9.0
end program test

파생 타입

파생 타입은 사용자 정의 데이터 구조(구조체). %로 구성 요소 접근. 타입 바인딩 프로시저는 OOP 활성화. 생성자는 인스턴스 생성. 파생 타입은 기본값을 가질 수 있고 다른 타입 확장(상속). 복잡한 데이터 모델링에 사용.

fortran
type :: Point
    real :: x, y
end type Point
type(Point) :: p
p = Point(1.0, 2.0)  ! Constructor
p%x = 3.0  ! Component access
print *, p%x, p%y
! Type-bound procedures (OOP)
type :: Circle
    real :: radius
contains
    procedure :: area => circle_area
end type

Intent 속성

intent(in) 매개변수는 읽기 전용(수정 불가). intent(out)은 쓰기 전용(프로시저가 설정). intent(inout)은 읽기-쓰기. 컴파일러는 intent 위반 검사. 코드 명확성 향상과 최적화 활성화. 항상 intent 지정.

fortran
subroutine process(input, output, inout)
    integer, intent(in) :: input    ! Read-only
    integer, intent(out) :: output  ! Write-only
    integer, intent(inout) :: inout ! Read-write
    output = input * 2
    inout = inout + 1
end subroutine

순수 & 기본적

pure 함수는 부작용 없음(I/O 없음, 가변 상태 없음). 컴파일러가 최적화 가능. elemental 함수는 스칼라와 배열 모두에서 자동 작동. 기본적으로 pure. 수학 연산에 이상적. 병렬 실행 활성화.

fortran
pure function square(x) result(y)
    real, intent(in) :: x
    real :: y
    y = x * x
end function
! Elemental: works on scalars and arrays
elemental function double_it(x) result(y)
    real, intent(in) :: x
    real :: y
    y = 2.0 * x
end function
! double_it([1,2,3]) returns [2,4,6]
21

배열

배열 선언

Fortran 배열은 기본적으로 1-인덱스. (0:)로 사용자 정의 하한. 열 우선 순서(첫 번째 인덱스가 가장 빨리 변화). allocatable 배열은 힙 할당되고 할당 해제 필요. 배열 상수는 [ ] 사용. Fortran 배열은 디스크립터로 인해 C 배열보다 더 효율적.

fortran
! Static arrays
real :: a(10)           ! 1D, indices 1-10
real :: b(0:9)          ! 1D, indices 0-9
real :: c(3, 4)         ! 2D, 3 rows x 4 cols
! Allocatable (dynamic)
real, allocatable :: d(:)
allocate(d(100))        ! Allocate
deallocate(d)           ! Free
! Array constants
integer :: nums(5) = [1, 2, 3, 4, 5]

배열 연산

Fortran은 전체 배열 연산 지원: +, -, *, /, **. 요소별 수학에 루프 불필요. 내장 함수: sum, product, maxval, minval, any, all, count. 벡터화로 인해 루프보다 훨씬 빠름. 이것이 수치 컴퓨팅을 위한 Fortran의 강점.

fortran
real :: a(5) = [1, 2, 3, 4, 5]
real :: b(5)
b = a * 2          ! Element-wise: [2,4,6,8,10]
b = a + 1          ! [2,3,4,5,6]
print *, sum(a)    ! 15
print *, maxval(a) ! 5
print *, any(a > 3)  ! .true.
print *, count(a > 2) ! 3

배열 섹션

배열 섹션은 (start:end:stride) 구문 사용. ::는 기본값 의미(1에서 끝까지, stride 1). 음수 stride는 역순. 다차원 섹션은 모든 차원에서 작동. 섹션은 프로시저에 전달 가능. 복사 없이 슬라이싱에 매우 강력.

fortran
real :: a(10) = [(i, i=1,10)]
print *, a(3:7)     ! Elements 3 to 7
print *, a(::2)     ! Every other: [1,3,5,7,9]
print *, a(2:8:2)   ! Stride 2: [2,4,6,8]
real :: m(3,3)
m(:, 2)             ! Second column
m(1, :)             ! First row

where 구문

where는 배열 수준 조건부 할당. 벡터화된 if처럼. Elsewhere는 거짓 케이스 처리. 벡터화 가능하므로 루프보다 더 효율적. 배열에 요소별 조건 연산에 사용.

fortran
real :: a(5) = [1, -2, 3, -4, 5]
where (a > 0)
    a = a * 2       ! Double positives
elsewhere
    a = 0           ! Zero negatives
end where
! Result: [2, 0, 6, 0, 10]
! Equivalent to a loop with if

동적 할당

allocatable 배열은 동적 크기. allocate로 생성, deallocate로 해제. Fortran 2003+은 범위 끝에서 자동 할당 해제. allocated()로 할당 상태 확인. Fortran 2003+에서 할당 시 자동 재할당. C malloc/free보다 훨씬 안전.

fortran
real, allocatable :: matrix(:,:)
integer :: n
n = 100
allocate(matrix(n, n))
matrix = 0.0  ! Initialize all to 0
! ... use matrix ...
deallocate(matrix)
! Automatic deallocation at end of scope
! (Fortran 2003+)
22

I/O 연산

서식 있는 출력

형식 문자열이 출력 제어. I5 = 정수 너비 5. F8.3 = 부동소수점 너비 8, 소수 3자리. I0 = 최소 너비. A = 문자열. X = 공백. / = 줄바꿈. write(*,...)는 print와 같지만 더 유연. 정렬된 출력을 위해 형식 문자열 사용.

fortran
integer :: i = 42
real :: x = 3.14159
print "(I5, F8.3)", i, x    ! "   42    3.142"
print "(A, I0)", "Count=", i  ! "Count=42"
write(*, "(3F6.2)") 1.0, 2.0, 3.0
! Format specifiers:
! I: integer, F: float, E: exponential
! A: string, X: space, /: newline

파일 I/O

open은 파일을 유닛에 연결. newunit은 여유 유닛 번호 할당. status: old(존재해야 함), new(존재하지 않아야 함), replace. iostat는 오류나 EOF 시 0이 아닌 값 반환. 크래시 방지를 위해 항상 iostat 확인. close는 파일 연결 해제.

fortran
integer :: unit, ios
open(newunit=unit, file="data.txt", status="old", action="read")
do
    read(unit, *, iostat=ios) value
    if (ios /= 0) exit
    print *, value
end do
close(unit)
! status: old, new, replace, scratch
! action: read, write, readwrite

Namelist

namelist는 I/O를 위해 변수 그룹화. 파일 형식은 &config n=10, x=3.14 /. 설정 파일에 유용. 변수는 어떤 순서든 가능. 나열된 변수만 읽기/쓰기. 사용자 정의 형식 파싱보다 훨씬 쉬움.

fortran
integer :: n = 10
real :: x = 3.14
namelist /config/ n, x
! Write namelist
open(1, file="config.nml")
write(1, nml=config)
close(1)
! Read namelist
open(1, file="config.nml")
read(1, nml=config)
close(1)

내부 파일

내부 파일은 문자열을 I/O 유닛으로 사용. 문자열에 write하면 값을 텍스트로 변환. 문자열에서 read하면 텍스트 파싱. 타입 변환과 서식에 유용. trim은 후행 공백 제거. C sprintf/sscanf보다 훨씬 단순.

fortran
character(20) :: str
integer :: num = 42
! Integer to string
write(str, "(I0)") num
print *, trim(str)  ! "42"
! String to integer
read(str, *) num
! Internal files use character variables as units

바이너리 I/O

비서식 I/O는 원시 바이너리 데이터 작성. 텍스트보다 빠르고 더 소형. access="stream"은 바이트 수준 접근(Fortran 2008). 아키텍처 간 이식 불가(엔디안). 대규모 과학 데이터셋에 사용. 서식 있는 I/O는 사람이 읽을 수 있는 데이터용.

fortran
! Unformatted (binary) I/O
open(1, file="data.bin", form="unformatted", &
     access="stream")
write(1) array  ! No format, raw bytes
read(1) array2
close(1)
! Faster than formatted I/O
! Smaller file size
! Not portable across architectures
23

병렬 프로그래밍

OpenMP

OpenMP는 지시문으로 루프를 병렬화. !$omp parallel do는 반복을 스레드에 분산. private: 각 스레드가 자체 복사본 보유. reduction: 결과 결합. -fopenmp로 컴파일. 수치 코드를 병렬화하는 쉬운 방법.

fortran
!$omp parallel do private(i) reduction(+:sum)
do i = 1, n
    sum = sum + a(i) * b(i)
end do
!$omp end parallel do
! Compile: gfortran -fopenmp program.f90
! Environment: OMP_NUM_THREADS=4

Coarray

Coarray(Fortran 2008)는 내장 병렬 배열. 각 이미지(프로세스)는 자체 복사본 보유. [N]은 다른 이미지의 데이터 접근. sync all은 장벽. this_image()는 이미지 번호 반환. 언어에 내장, 라이브러리 불필요.

fortran
program coarray_example
    implicit none
    integer :: me[*]  ! Coarray: one per image
    me = this_image()
    sync all  ! Barrier
    if (this_image() == 1) then
        print *, "Image 2 has:", me[2]  ! Remote access
    end if
end program
! Compile: gfortran -fcoarray=single program.f90

MPI 기본

MPI(Message Passing Interface)는 분산 병렬 처리의 표준. mpi_init/finalize로 시작과 끝. comm_rank는 프로세스 ID 제공. comm_size는 총 프로세스 제공. 통신은 Send/recv. 수천 코어로 확장. 클러스터에 사용.

fortran
program mpi_example
    use mpi
    integer :: rank, size, ierr
    call mpi_init(ierr)
    call mpi_comm_rank(MPI_COMM_WORLD, rank, ierr)
    call mpi_comm_size(MPI_COMM_WORLD, size, ierr)
    print *, "I am rank", rank, "of", size
    call mpi_finalize(ierr)
end program
! Compile: mpifort program.f90

do concurrent

do concurrent(Fortran 2008)는 루프 반복이 독립적임을 표시. 컴파일러가 자동으로 병렬화 가능. local은 private 변수 선언. OpenMP보다 안전: 컴파일러가 독립성 검증. 당혹스럽게 병렬 루프에 사용.

fortran
do concurrent (i = 1:n) local(tmp)
    tmp = expensive_compute(a(i))
    b(i) = tmp * 2
end do
! Tells compiler iterations are independent
! Can be parallelized automatically
! local: private variable per iteration

축소 패턴

축소는 각 스레드의 부분 결과 결합. 일반: sum, product, max, min. 각 스레드는 로컬 부분 결과 계산. 런타임이 끝에서 결합. 데이터 경쟁 방지. 병렬 수치 알고리즘에 필수.

fortran
!$omp parallel do reduction(+:total)
do i = 1, n
    total = total + a(i)
end do
!$omp end parallel do
! Common reductions: +, *, max, min, .and., .or.
! Each thread has a private copy
! Combined at the end
24

수치 방법

선형 대수

Fortran에는 내장 행렬 연산. matmul은 행렬 곱셈. dot_product는 내적 계산. transpose는 전치. 이들은 고도로 최적화됨(BLAS 수준). 프로덕션에는 LAPACK 사용. Fortran은 고성능 수치 컴퓨팅을 위한 선택 언어.

fortran
! Matrix multiplication
do i = 1, n
    do j = 1, n
        c(i,j) = sum(a(i,:) * b(:,j))
    end do
end do
! Or use matmul intrinsic
c = matmul(a, b)
! Dot product
dot = dot_product(a, b)
! Transpose
at = transpose(a)

ODE 해결

오일러 방법은 가장 단순한 ODE 해결기: y(n+1) = y(n) + dt*f(t,y). 정확도를 위해 Runge-Kutta(RK4) 사용. 인터페이스 블록은 함수를 인수로 전달. 배열 연산과 성능으로 인해 Fortran은 과학 컴퓨팅에 이상적.

fortran
! Euler method: dy/dt = f(t, y)
subroutine euler(f, t0, y0, dt, n, t, y)
    interface
        real function f(t, y)
            real, intent(in) :: t, y
        end function
    end interface
    real, intent(in) :: t0, y0, dt
    integer, intent(in) :: n
    real, intent(out) :: t(n+1), y(n+1)
    integer :: i
    t(1) = t0; y(1) = y0
    do i = 1, n
        t(i+1) = t(i) + dt
        y(i+1) = y(i) + dt * f(t(i), y(i))
    end do
end subroutine

난수

random_number는 균등 [0,1) 실수 생성. random_seed는 생성기 초기화. 정수의 경우 스케일하고 변환. Box-Muller는 균등을 정규 분포로 변환. 심각한 작업에는 라이브러리(예: Mersenne Twister) 사용. 재현성을 위해 항상 시드.

fortran
call random_seed()  ! Seed from system
call random_number(x)  ! x in [0, 1)
! Array of randoms
real :: arr(100)
call random_number(arr)
! Integer in range [1, 6]
integer :: dice
call random_number(r)
dice = int(r * 6) + 1
! Normal distribution (Box-Muller)
call random_number(u1)
call random_number(u2)
z = sqrt(-2*log(u1)) * cos(2*PI*u2)

보간

선형 보간은 알려진 점 사이의 값 추정. 구간 찾기, 그다음 보간. 더 부드러운 결과를 위해 3차 스플라인 보간 사용. Fortran의 배열 연산은 이것을 간결하게 만듦. 외삽 오류 방지를 위해 항상 범위 확인.

fortran
function interp(x, xs, ys) result(y)
    real, intent(in) :: x, xs(:), ys(:)
    real :: y
    integer :: i
    ! Find interval
    i = 1
    do while (i < size(xs) .and. x > xs(i+1))
        i = i + 1
    end do
    ! Linear interpolation
    y = ys(i) + (ys(i+1) - ys(i)) * &
        (x - xs(i)) / (xs(i+1) - xs(i))
end function

수치 적분

사다리꼴 규칙은 적분 근사: 사다리꼴의 합. 더 정확: 심프슨 규칙. 더 높은 차원의 경우 가우스 구적법 사용. 성능으로 인해 Fortran은 수치 적분에 뛰어남. 항상 알려진 해석적 해로 검증.

fortran
! Trapezoidal rule
function trapezoid(f, a, b, n) result(integral)
    interface
        real function f(x)
            real, intent(in) :: x
        end function
    end interface
    real, intent(in) :: a, b
    integer, intent(in) :: n
    real :: integral, h
    integer :: i
    h = (b - a) / n
    integral = (f(a) + f(b)) / 2
    do i = 1, n-1
        integral = integral + f(a + i*h)
    end do
    integral = integral * h
end function
25

일반적인 함정

1-기반 인덱싱

Fortran 배열은 기본적으로 1-인덱스, C/Python(0-인덱스)과 달리. 코드 이식 시 오프바이원 오류 발생. 사용자 정의 하한(0:9) 허용. 프로젝트 내에서 일관되게 유지. -fcheck=bounds 컴파일러 플래그로 배열 범위 확인.

fortran
! Fortran arrays are 1-based by default
real :: a(10)  ! Indices 1 to 10
! a(0) = 1.0  ! Error: out of bounds
a(1) = 1.0    ! OK
! Custom bounds
real :: b(0:9)  ! Indices 0 to 9
b(0) = 1.0    ! OK

암묵적 타이핑

implicit none 없이 i-n으로 시작하는 변수는 정수, 다른 것은 실수. 이것은 미묘한 버그 발생(오타가 새 변수 생성). 항상 implicit none 사용. 현대 Fortran(2018+)은 -fimplicit-none으로 전역 설정 가능. 이것이 가장 중요한 Fortran 모범 사례.

fortran
! BAD: implicit typing (Fortran 77 style)
program bad
    ! i-n start with integer by default
    i = 1      ! integer (implicit)
    x = 3.14   ! real (implicit)
end program
! GOOD: explicit typing
program good
    implicit none  ! Force explicit declaration
    integer :: i
    real :: x
end program

열 우선 순서

Fortran은 배열을 열 우선으로 저장: m(1,1), m(2,1), m(3,1), m(1,2), ... 열별로 접근하는 것이 캐시 친화적. 잘못된 루프 순서는 캐시 미스 발생과 10배 이상 느려짐. 루프 순서를 항상 메모리 레이아웃에 일치. C(행 우선)의 반대.

fortran
! Fortran is column-major (first index varies fastest)
real :: m(3, 3)
! Efficient: iterate over first index in inner loop
do j = 1, 3
    do i = 1, 3
        m(i, j) = 0.0  ! Cache-friendly
    end do
end do
! Inefficient: row-major access
do i = 1, 3
    do j = 1, 3
        m(i, j) = 0.0  ! Cache misses
    end do
end do

참조에 의한 전달

Fortran은 인수를 참조로 전달(C 포인터처럼). intent(in)이 지정되지 않으면 서브루틴이 호출자 변수 수정 가능. intent 없이는 우발적 수정이 버그 발생. 항상 intent 지정. intent(out)은 프로시저가 값을 설정할 것을 신호.

fortran
subroutine modify(x)
    integer, intent(inout) :: x
    x = 99  ! Modifies the caller variable
end subroutine
! Fortran passes by reference by default
! intent(in) prevents modification
! Without intent, modification is allowed (dangerous)

부동소수점 정밀도

기본 real은 단정밀도(~7자릿수), 종종 불충분. 과학 컴퓨팅에는 배정밀도 사용. kind(1.0d0) 또는 selected_real_kind(15)로 배정밀도 정의. 항상 리터럴에 접미사: 3.14_dp. 정밀도 혼합은 조용한 잘림 발생. 이식 가능한 kind에는 iso_fortran_env 사용.

fortran
! Single precision (default)
real :: x = 3.14159  ! ~7 digits
! Double precision
real(kind=8) :: y = 3.14159d0  ! ~15 digits
! Or use kind parameter
integer, parameter :: dp = kind(1.0d0)
real(dp) :: z = 3.14159_dp
! Always use _dp or d0 for double literals

Was this helpful?