Fortran · 기본
계산으로 배우는 현대 Fortran
Fortran 시작하기 - 컴파일하고 실행하는 첫 계산
Fortran 이 아직 쓰이는 곳(기상·유체·HPC), gfortran 설치 확인, program·end program 구조, implicit none, 컴파일·실행, print 와 리스트 지향 출력
개발자KR · 원고 갱신
이 장에서 배우는 것
이 책은 작은 기상 관측소의 시간별 기온과 강수 자료를 계산하는 일에서 출발해, 마지막 장에서 막대의 열 전달을 시뮬레이션하는 프로그램까지 키워 간다. 첫 장에서는 계산 자체보다 도구를 먼저 확인한다. 소스 파일을 하나 쓰고, 컴파일러로 번역하고, 실행해서 화면에 결과가 나오는 한 바퀴를 직접 돌려 본다. 이 한 바퀴가 손에 익으면 이후 장의 내용은 모두 같은 틀 위에 얹힌다.
- Fortran 이 지금도 쓰이는 분야와 그 이유를 설명할 수 있다.
- gfortran 이 설치되어 있는지 확인하고, 없으면 설치 방법을 찾을 수 있다.
program과end program으로 이루어진 프로그램의 뼈대를 쓸 수 있다.implicit none이 왜 필요한지 설명하고 항상 첫머리에 적는다.gfortran -std=f2018 -Wall로 컴파일하고,print의 두 가지 출력 방식(리스트 지향과 서식 지정)을 구분해서 쓴다.
문제 상황
동네 언덕에 작은 기상 관측소가 있다고 하자. 관측소는 한 시간에 한 번 기온(섭씨)과 그 시간의 강수량(밀리미터)을 기록한다. 하루가 지나면 기록이 24줄 쌓인다. 담당자는 이 기록으로 평균 기온과 하루 강수 합계를 구해 보고서에 적어야 한다. 처음에는 계산기로 충분하지만, 관측소가 늘고 기록이 쌓이면 손으로 반복하기 어렵다. 같은 계산을 프로그램으로 만들어 두면 자료가 바뀌어도 같은 방식으로 다시 계산할 수 있다.
그런데 왜 하필 Fortran 인가. 이 질문에는 현실적인 답이 있다. 기상 예보와 기후 모델, 유체(공기와 물처럼 흐르는 물질)의 움직임을 계산하는 프로그램, 대형 계산 시설(HPC, high-performance computing)에서 돌아가는 과학 계산 코드 가운데에는 지금도 Fortran 으로 쓰인 것이 많다. 행렬 계산을 맡는 유명한 선형대수 라이브러리의 기준 구현도 Fortran 으로 작성되어 있다. 이 언어는 1950년대 후반에 수식 계산을 위해 만들어졌고, 이후 표준이 여러 번 개정되어(77, 90, 95, 2003, 2008, 2018, 2023) 지금의 모습이 되었다. 이 책이 따르는 표준은 2018 년판이다.
오래 쓰인 이유는 단순하다. 배열과 수치 계산이 언어의 기본 기능으로 들어 있고, 컴파일러가 이를 빠른 기계어로 바꾸는 데 오랫동안 공을 들여 왔다. 이미 검증된 계산 코드가 쌓여 있어서 새로 쓰기보다 이어 쓰는 편이 합리적인 경우도 많다. 그래서 과학 계산 현장에서는 Fortran 을 읽고 고쳐야 할 일이 계속 생긴다. 이 책은 오래된 방식이 아니라 현대 Fortran 의 문법으로 처음부터 쓴다.
이 장의 목표는 작다. 세 시간의 기온 평균과 강수 합계를 계산해서 화면에 보고서 형태로 출력하는 프로그램을 만든다. 변수는 다음 장에서 배우므로 이 장에서는 숫자를 식에 직접 적는다.
개념 설명
컴파일러와 gfortran 확인
Fortran 소스 파일은 사람이 읽는 글이다. 컴퓨터가 실행하려면 기계가 이해하는 형태로 번역해야 하고, 이 번역을 하는 프로그램이 컴파일러(compiler)다. 번역의 결과로 나오는 파일을 실행 파일이라 한다. 이 책은 GNU 프로젝트의 컴파일러 gfortran 을 쓴다. 터미널에서 아래 명령으로 설치 여부와 버전을 확인한다.
gfortran --version
첫 줄에 GNU Fortran 으로 시작하는 문구와 버전 번호가 나오면 준비된 것이다. 이 책의 예제는 16 버전을 기준으로 하지만, 2018 표준을 지원하는 최근 버전이라면 같은 방식으로 따라 할 수 있다. 명령을 찾을 수 없다는 메시지가 나오면 설치한다. macOS 에서는 Homebrew 로 brew install gcc 를 실행하면 gfortran 이 함께 설치된다. 이때 macOS 에 기본으로 있는 gcc 명령은 다른 컴파일러를 가리키는 경우가 있으므로, Fortran 에는 반드시 gfortran 이라는 이름을 직접 쓴다. Debian 계열 Linux 에서는 sudo apt install gfortran, Fedora 계열에서는 sudo dnf install gcc-gfortran 이 일반적이다. 자세한 설치 안내는 GFortran 공식 위키에서 확인할 수 있다.
소스 파일에서 실행까지
작업의 흐름은 두 단계다. 먼저 컴파일러가 소스를 번역해 실행 파일을 만들고, 그다음 그 실행 파일을 실행한다. 소스를 고칠 때마다 번역을 다시 해야 고친 내용이 실행 파일에 반영된다.
이 책은 소스 파일의 확장자로 .f90 를 쓴다. 숫자 90 은 이 형식이 도입된 표준의 연도에서 왔지만, 현재의 최신 문법도 같은 확장자로 쓴다. 이 확장자는 컴파일러에게 자유 형식(free form)이라는 신호가 되기도 한다. 자유 형식은 줄의 어느 칸에서 시작해도 되고 들여쓰기가 자유롭다는 뜻이다. 아주 오래된 소스는 몇 번째 칸에 무엇을 써야 하는지 정해진 고정 형식이었는데, 이 형식의 파일은 .f 확장자를 쓴다. 새로 쓰는 코드에서는 고정 형식을 쓸 이유가 없다.
컴파일 옵션
컴파일 명령에는 옵션을 붙인다. 이 책에서는 모든 예제를 다음 명령으로 번역한다.
gfortran -std=f2018 -Wall main.f90 -o report
| 조각 | 뜻 | 쓰는 이유 |
|---|---|---|
gfortran | 컴파일러 실행 | 번역을 맡는다 |
-std=f2018 | 2018 표준 문법만 허용 | 표준에서 벗어난 확장 문법을 쓰면 알려 준다 |
-Wall | 흔한 실수에 대한 경고를 켠다 | 쓰지 않는 변수나 의심스러운 코드를 일찍 발견한다 |
main.f90 | 번역할 소스 파일 | 입력이다 |
-o report | 결과 실행 파일 이름 | 생략하면 a.out 이라는 이름이 붙는다 |
경고(warning)는 오류(error)와 다르다. 오류가 있으면 번역이 멈추고 실행 파일이 만들어지지 않는다. 경고는 번역은 되지만 의심스럽다는 알림이다. 이 책의 완성 코드는 경고도 없이 번역되도록 쓴다. 경고를 무시하는 습관은 나중에 찾기 어려운 버그로 이어지므로, 처음부터 경고가 하나도 없는 상태를 기본으로 삼는 편이 좋다.
프로그램의 뼈대: program 과 end program
Fortran 프로그램은 program 문으로 시작해서 end program 문으로 끝난다. 둘 사이에 컴퓨터가 위에서 아래로 한 줄씩 수행할 문장(실행문)을 적는다. program 뒤에는 프로그램 이름을 붙이고, end program 뒤에도 같은 이름을 적는 것이 읽기 좋다. 이름을 적는 쪽이 길지만, 코드가 길어졌을 때 어느 끝인지 한눈에 알 수 있다. 이름이 서로 다르면 컴파일러가 오류를 낸다.
Fortran 은 대문자와 소문자를 구별하지 않는다. PRINT 와 print 는 같은 문장이다. 이 책은 읽기 쉽도록 키워드를 소문자로 쓴다. 느낌표(!) 뒤는 주석으로, 컴파일러가 무시하고 사람을 위한 설명으로만 쓰인다. 한 줄에 문장 하나를 쓰는 것이 기본이고, 한 줄은 132자까지 쓸 수 있다.
implicit none
뼈대의 두 번째 줄에는 항상 implicit none 을 적는다. 이 문장의 뜻을 이해하려면 Fortran 의 오래된 규칙을 알아야 한다. 이 언어는 선언하지 않은 이름을 만나면 첫 글자로 자료형을 짐작하는 규칙(암묵적 타입, implicit typing)을 갖고 있다. 이름이 i 부터 n 사이의 글자로 시작하면 정수, 그 밖에는 실수로 취급한다. 수식을 짧게 쓰던 시절에는 편리했지만, 철자를 한 글자 틀리면 새 변수가 조용히 만들어지는 부작용이 있다. 틀린 줄은 오류도 경고도 없이 번역되고, 결과만 이상해진다.
implicit none 은 이 규칙을 끈다. 그러면 모든 이름을 직접 선언해야 하고, 선언하지 않은 이름은 컴파일 오류가 된다. 철자 실수를 번역 단계에서 잡아 주는 가장 값싼 안전장치다. 변수를 선언하는 방법은 다음 장에서 자세히 배운다. 이 장의 프로그램에는 변수가 없지만, 습관을 들이기 위해 첫 장부터 적는다.
print 와 출력 방식 두 가지
화면에 무언가를 쓰려면 print 문을 쓴다. 형식은 print 형식, 출력할 것들 이다. 형식 자리에 별표(*)를 쓰면 리스트 지향 출력(list-directed output)이 된다. 출력 모양을 컴파일러에게 맡긴다는 뜻이다.
print *, 'Weather station Hill-01'
문자열은 작은따옴표나 큰따옴표로 감싼다. 이 출력의 맨 앞에는 빈칸이 하나 붙는다. 과거 프린터에서 첫 칸을 제어 문자로 쓰던 관습의 흔적이며, gfortran 은 이 빈칸을 그대로 출력한다. 문자열만 출력할 때는 이 빈칸 하나를 빼면 나머지는 예상한 대로다. 하지만 숫자를 리스트 지향으로 출력하면 자릿수와 앞쪽 빈칸의 수를 컴파일러가 정한다. 같은 숫자도 컴파일러나 자료형에 따라 길이가 달라질 수 있어서, 보고서처럼 모양이 정해져야 하는 출력에는 맞지 않는다.
그래서 숫자를 보여 줄 때는 서식 지정(format)을 쓴다. 형식 자리에 괄호로 묶은 문자열을 넣고, 그 안에 출력 칸의 모양을 적는다.
print '(A, F8.2)', 'Mean temperature (C):', 13.3333
| 지정자 | 대상 | 뜻 | 예 |
|---|---|---|---|
A | 문자열 | 문자열 길이만큼 그대로 출력 | A |
I4 | 정수 | 폭 4칸에 오른쪽 맞춤 | 3 은 3 |
F8.2 | 실수 | 전체 폭 8칸, 소수점 아래 2자리 | 13.3333 은 13.33 |
서식의 항목은 출력할 것과 앞에서부터 하나씩 짝을 이룬다. 위 예에서 A 는 문자열과, F8.2 는 실수와 짝이다. 폭은 소수점과 부호를 포함한 전체 칸 수이고, 값이 폭보다 짧으면 왼쪽을 빈칸으로 채운다. 값이 폭보다 길면 칸을 별표로 채워 넘침을 알려 준다. 서식은 파일 입출력을 다루는 장에서 다시 정리하므로, 지금은 이 세 가지만 알아 두면 된다.
한 가지 더 알아 둘 것이 있다. 이 책의 코드는 문자열을 ASCII 문자(영문, 숫자, 기호)로만 쓴다. 한글은 한 글자가 여러 바이트를 차지해서 글자 수와 칸 수가 어긋나고, 서식의 폭 계산과 맞지 않을 수 있다. 주석은 컴파일러가 읽지 않으므로 한글로 써도 된다.
완성 코드
아래 파일이 이 장의 전부다. 에디터로 main.f90 이름으로 저장한다.
main.f90
program first_report
implicit none
! 관측소 이름과 보고서 제목: 리스트 지향 출력
print *, 'Weather station Hill-01'
print *, 'First calculation: three hourly readings'
! 세 시간의 기온(섭씨) 평균: 서식 지정 출력
print '(A, F8.2)', 'Mean temperature (C):', (12.5 + 14.0 + 13.5) / 3.0
! 관측한 시간 수
print '(A, I4)', 'Observation hours:', 3
! 세 시간의 강수량(mm) 합계
print '(A, F8.2)', 'Rainfall total (mm):', 0.4 + 1.2 + 0.0
end program first_report
줄별 해설
program first_report 는 프로그램의 시작이다. 이름 first_report 는 영문자로 시작하고 영문자, 숫자, 밑줄로 이루어진다. 공백은 쓸 수 없어서 단어 사이는 밑줄로 잇는다.
implicit none 은 암묵적 타입 규칙을 끈다. 선언문(변수를 정하는 문장)이 있다면 그보다 앞에, 즉 program 바로 다음 줄에 와야 한다.
빈 줄과 ! 로 시작하는 줄은 읽는 사람을 위한 것이다. 컴파일러는 건너뛴다. 빈 줄로 블록을 나누고 한 줄 주석으로 의도를 밝히는 습관은 코드가 길어졌을 때 큰 차이를 만든다.
첫 두 print *, ... 는 문자열을 리스트 지향으로 출력한다. 줄 끝에서 줄바꿈이 자동으로 일어나므로 다음 print 는 새 줄에서 시작한다.
print '(A, F8.2)', 'Mean temperature (C):', (12.5 + 14.0 + 13.5) / 3.0 는 서식을 쓴다. 출력할 것은 둘이다. 문자열은 A 가 맡고, 계산 결과는 F8.2 가 맡는다. 괄호 안의 식은 먼저 계산된다. 합은 40.0 이고 3.0 으로 나누면 13.3333… 이며 소수점 아래 둘째 자리까지 반올림해 13.33 이 된다. F8.2 는 폭 8칸이므로 앞에 빈칸 세 개가 붙는다. 문자열 끝의 콜론 뒤에는 따로 빈칸을 쓰지 않았는데도 값과 떨어져 보이는 이유가 이 빈칸이다. 여기서 / 는 나눗셈이고 * 는 곱셈이다. 정수끼리 나눌 때 생기는 문제는 식을 다루는 장에서 다룬다. 이 장에서는 12.5 처럼 소수점이 있는 수만 나눈다.
print '(A, I4)', 'Observation hours:', 3 는 정수 3 을 폭 4칸에 오른쪽 맞춤으로 출력한다. 소수점이 없는 3 은 정수이고, 소수점이 있는 3.0 은 실수다. 이 구분은 다음 장에서 본격적으로 다룬다. 정수에 F8.2 를 쓰거나 실수에 I4 를 쓰면 실행 중에 오류가 나므로, 값의 종류와 지정자의 종류를 맞춘다.
print '(A, F8.2)', 'Rainfall total (mm):', 0.4 + 1.2 + 0.0 는 강수량 세 값을 더한다. 합은 1.6 이고 F8.2 로 쓰면 1.60 이 된다. 0.4 와 1.2 는 컴퓨터 안에서 이진수로 저장되어 정확히 표현되지 않지만, 둘째 자리까지만 보이게 하므로 출력은 항상 1.60 이다. 이 오차의 정체는 식을 다루는 장에서 자세히 본다.
end program first_report 는 프로그램의 끝이다. 여기에 도달하면 프로그램이 정상적으로 종료한다.
실행 결과
터미널에서 main.f90 가 있는 폴더로 이동해 컴파일하고 실행한다. 컴파일이 성공하면 아무 메시지도 출력되지 않고 프롬프트가 돌아온다. 경고가 하나라도 나오면 완성 코드와 어딘가 다르다는 뜻이므로 오타를 확인한다.
$ gfortran -std=f2018 -Wall main.f90 -o report
$ ./report
Weather station Hill-01
First calculation: three hourly readings
Mean temperature (C): 13.33
Observation hours: 3
Rainfall total (mm): 1.60
첫 두 줄의 앞에 빈칸이 하나씩 있고, 서식을 쓴 세 줄에는 없다는 점을 확인한다. 리스트 지향 출력이 앞에 빈칸을 붙이기 때문이다. 서식을 쓴 줄은 우리가 적은 칸 모양대로만 나온다. ./report 의 ./ 는 현재 폴더에 있는 파일을 실행하라는 뜻이다.
실무에서 자주 틀리는 것
implicit none 을 빼먹고 이름을 잘못 쓴다
틀린 코드는 다음과 같다. total 을 선언해 놓고 출력에서는 totl 로 썼다. 선언문은 다음 장에서 배우므로 지금은 정수 변수를 만드는 문장이라고만 이해하면 된다.
program typo
integer :: total
total = 10
print *, totl
end program typo
implicit none 이 없으므로 totl 은 값을 한 번도 받지 않은 새 실수 변수로 취급되어 번역이 된다. 출력은 0 이거나 임의의 값이며, -Wall 이 값이 정해지지 않은 채 쓰였다는 경고를 낼 수는 있지만 오류는 아니다. 고친 코드는 implicit none 을 추가한 것이다.
program typo
implicit none
integer :: total
total = 10
print *, total
end program typo
이제 철자를 틀리면 선언되지 않은 이름이라는 오류로 번역 단계에서 멈춘다. 오류 메시지의 줄 번호를 따라가면 오타를 바로 찾는다.
고정 형식 확장자 .f 로 저장한다
파일 이름을 hello.f 로 저장하면 gfortran 은 고정 형식으로 읽는다. 고정 형식에서는 1~6번째 칸이 줄 번호와 이어 쓰기 표시를 위한 자리다. 아래처럼 두 칸만 들여 쓴 코드는 이 자리에 문장이 들어가서 오류가 난다.
! hello.f 로 저장하면 오류가 난다
program hello
print *, 'hi'
end program hello
고치는 방법은 파일 이름을 hello.f90 로 바꾸는 것이다. 이 책의 모든 파일은 .f90 를 쓴다.
예전 실행 파일을 다시 실행한다
컴파일에 실패하면 새 실행 파일이 만들어지지 않는다. 그런데 이전에 만든 report 가 폴더에 남아 있으면 ./report 는 아무 문제 없이 실행되고, 소스를 고친 결과가 보이지 않아 한참 헤맨다. 컴파일 명령 다음에는 오류 메시지가 없었는지를 먼저 확인하고, 그다음에 실행한다. 두 명령을 한 줄로 이어 쓰면 이 실수를 막을 수 있다.
gfortran -std=f2018 -Wall main.f90 -o report && ./report
&& 는 앞 명령이 성공했을 때만 뒤 명령을 실행하게 한다. 또 report 라고만 입력하면 현재 폴더는 명령을 찾는 경로에 들어 있지 않아서 명령을 찾을 수 없다는 메시지가 나온다. 반드시 ./report 로 쓴다.
리스트 지향 출력으로 자릿수가 고정되기를 기대한다
평균 기온을 이렇게 출력했다고 하자.
print *, (12.5 + 14.0 + 13.5) / 3.0
화면에는 소수점 아래가 일곱 자리쯤 이어지는 긴 숫자가 나온다. 자릿수와 앞쪽 빈칸 수는 컴파일러가 정하므로, 보고서에 붙이거나 다른 출력과 비교하는 용도로는 맞지 않는다. 필요한 자릿수가 있으면 서식으로 고정한다.
print '(A, F8.2)', 'Mean temperature (C):', (12.5 + 14.0 + 13.5) / 3.0
한눈에 보기
| 항목 | 쓰는 법 | 핵심 |
|---|---|---|
| 설치 확인 | gfortran --version | GNU Fortran 과 버전이 나오면 준비 완료 |
| 프로그램 뼈대 | program 이름 … end program 이름 | 이름을 양쪽에 같게 쓴다 |
| 암묵 규칙 끄기 | implicit none | 항상 program 바로 아래에 쓴다 |
| 컴파일 | gfortran -std=f2018 -Wall main.f90 -o report | 경고 없이 번역되게 쓴다 |
| 실행 | ./report | ./ 를 빼먹지 않는다 |
| 리스트 지향 출력 | print *, '문자열' | 모양은 컴파일러가 정하고 앞에 빈칸이 붙는다 |
| 서식 지정 출력 | print '(A, F8.2)', 문자열, 실수 | 자릿수가 고정되어 출력이 결정적이다 |
| 주석 | ! 설명 | 컴파일러는 무시한다 |
연습 문제
- 한 시간 간격으로 잰 기온 18.0, 21.0, 19.5, 17.5(섭씨) 네 값의 평균을 구해
Mean (C):라는 문구와 함께F8.2서식으로 출력하는 프로그램을 쓰라. 프로그램 이름은mean4로 한다. - 다음 코드를 컴파일하면 오류가 난다. 원인을 설명하고 고치라.
program test implicit none print *, 'rain' end program rain main.f90를-std=f2018 -Wall옵션으로 컴파일해 실행 파일 이름을today로 만들고, 곧바로 실행하는 명령 두 개를 쓰라.print '(A, F6.1)', 'T:', 3.14159가 출력할 한 줄을 정확히 쓰라. 빈칸의 수도 맞춰야 한다.
정답과 해설
1번. 네 값의 합은 76.0 이고 4.0 으로 나누면 19.0 이다.
program mean4
implicit none
print '(A, F8.2)', 'Mean (C):', (18.0 + 21.0 + 19.5 + 17.5) / 4.0
end program mean4
출력은 Mean (C): 19.00 이다. 문자열 뒤에 F8.2 의 앞쪽 빈칸 세 개가 붙는다. 나누는 수를 4.0 으로 쓴 것은 실수끼리 나누기 위해서다. 정수로 나눌 때의 차이는 식을 다루는 장에서 확인한다.
2번. program 문의 이름은 test 인데 end program 의 이름이 rain 으로 서로 다르다. 이름을 맞춰 end program test 로 고친다. 이름을 양쪽에 같게 쓰는 규칙이 있는 이유는 코드가 길어졌을 때 시작과 끝의 짝을 확인할 수 있게 하기 위해서다.
3번. 다음 두 명령이다. 한 줄로 이으려면 && 를 쓴다.
gfortran -std=f2018 -Wall main.f90 -o today
./today
4번. F6.1 은 전체 폭 6칸에 소수점 아래 한 자리다. 3.14159 는 반올림하면 3.1 이고 세 글자이므로 앞에 빈칸 세 개가 붙는다. 문자열 T: 는 그대로 나오므로 한 줄은 T: 3.1 이다. T: 와 3.1 사이의 빈칸은 세 개다.
READER FEEDBACK
질문·의견
내용에 관한 질문이나 더 나은 설명을 위한 의견을 남겨 주세요. 오탈자는 위의 제보 양식이 더 빨리 반영됩니다. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.
댓글 0
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.