[!attention] 이 장의 목표
- 기본 입출력 — read, write, print로 값을 읽고 쓴다.
- 서식 다루기 — format과 편집 기술자로 출력 모양을 정한다.
- 데이터 넘기기 — 결과를 csv로 출력해 Python으로 넘긴다.
- 예외 맛보기 — NaN과 Inf, ieee_arithmetic을 개요 수준에서 이해한다.
지금까지 우리는 연산 결과를 화면에 직관적으로 출력하는 print *, 한 가지 도구만 사용해 왔다. 이 단순한 방식은 빠르게 계산 값을 검증할 때는 유용하지만, 서로 다른 시스템 연동 단계에서는 다음 몇 가지 벽에 봉착한다.
1. 정밀 포맷 정렬
정수 $i$와 그에 따른 실수 $x$, $x^2$, $x^3$의 변화를 한눈에 들어오는 표(table) 형태로 출력하려 할 때, print *, 방식은 데이터마다 제각각의 칸 너비를 임의로 지정하기 때문에 열 정렬이 무너진다. 따라서, 출력 칸 너비(width)와 소수점 아래 표시 자릿수(precision)를 직접 지정하는 포맷 명령이 필요하다.
2. 파일 영구 저장 및 시각화 연동
계산 결과를 터미널 화면에만 출력하고 사라지는 방식으로는 데이터 분석이 어렵다. 연속적인 데이터 분석 및 시각화를 가능하게 하려면, 화면이 아닌 디스크 상의 물리 파일에 데이터를 기록하는 범용 표준 csv 포맷으로 줄을 맞추어 저장하는 것이 필요하다.
3. 결함 데이터(Inf/NaN) 전처리 필터링
수치 해석 연산 중 0으로 나누기 에러나 물리 법칙을 위배한 음수의 로그(Log) 계산 등이 발생하면, 하드웨어는 결함 또는 예외 데이터인 Inf (Infiinity) 또는 NaN (Not a Number) 신호를 데이터 버퍼에 보낸다. 이런 결함 데이터가 csv로 저장되면 후처리(데이터 분석이나 시각화) 단계에서 문제가 발생할 수 있다. 따라서 원치 않는 결함 데이터를 실행 시 사전에 걸러낼 수 있는 장치가 있어야 한다.
[!important] 6.1 목록 지정 입출력
Fortran에서 가용할 수 있는 가장 단순한 데이터 전송 방식은 목록 지정 입출력(list-directed I/O)이다. 이는 소스코드 내부에 구체적인 출력 서식을 기술하지 않고, 문자 * 하나만으로 세부적인 자릿수 표기와 칸 너비 설정은 컴파일러가 알아서 결정하는 방식이다.
print *, "x =", 3.14, "n =", 42
이 목록 지정 입출력의 큰 이점은 간편성과 신속성이다. 알고리즘 개발 도중 특정 변수의 수치 를 실시간으로 모니터링하거나, 서식에 맞춘 출력보다 수치의 무결성 검증이 우선일 때 편리하다.
이런 목록 지정 제어는 출력 뿐만 입력(read)에서도 동일한 방식으로 작동한다.
print *, 을 이용한 출력다음은 외부 환경으로부터 실수형 데이터 두 개를 전달 받아 산술 연산을 수행하는 구조이다.
%%writefile list_io.f90
program list_io
implicit none
real :: a, b
read *, a, b
print *, "sum =", a + b
print *, "diff =", a - b
end program list_io
컴파일·실행:
!gfortran -O2 -std=f2018 -Wall list_io.f90 -o list_io
목록 지정 입력문인 read *은 키보드 등 시스템의 표준 입력(standard input, stdin) 장치로부터 데이터를 파싱(parsing)하여 읽어 들인다. 구글 코랩이나 리눅스 터미널 처럼 대화형 입력 창을 구성하기 어려운 경우, 아래와 같이 쉘 명령어인 echo 와 파이프(|) 명령을 결합하여 프로그램에 필요한 값을 직접 전달할 수 있다.
실행:
!echo "3.5 1.5" | ./list_io
실행 결과:
sum = 5.00000000
diff = 2.00000000
구분자(delimiter)의 유연성: read * 의 파싱은 입력으로부터 유입되는 토큰(token)들을 분리할 때 유연한 규칙을 적용한다. 프로그램에 전달되는 수치 데이터가 3.5 1.5와 같이 단순 공백(space)으로 분리되거나, 3.5, 1.5처럼 쉼표(comma)로 구분되거나, 혹은 서로 다른 두 줄(line feed)에 나뉘어 입력되더라도 프로그램은 각각의 값을 해당 변수 a와 b에 정확하게 할당한다.
출력 제어권 상실의 한계: 반면, 목록 지정 출력 방식은 출력 형식을 프로그래머가 제어하기 어렵다는 단점이 있다. 앞선 실행 결과처럼 5.00000000이라는 숫자 좌우에 넓은 공백이 임의로 삽입되거나 소수점 이하 자릿수가 고정되는 현상은 전적으로 컴파일러의 내부 기본 설정에 따른 결과이다.
따라서 행과 열을 맞추어 정형화된 표(grid)를 구성하거나, 불필요한 소수점 이하 자릿수를 생략하여 가독성을 높이는 등 출력 레이아웃에 대한 통제권을 확보하려면 서식 지정(formatted) 입출력 기법이 수반되어야 한다.
[!important] 6.2 read · print · write
Fortran에서 가공된 데이터를 전송하고 수집하는 명령 체계는 데이터의 흐름 방향과 대상 장치에 따라 크게 세 가지로 구분된다.
read 문: 지정된 또는 표준 입력 장치(키보드)로부터 데이터를 스캔하여 읽어 들인 후, 프로그램 내부에 대기 중인 변수 버퍼 공간에 값을 저장한다.
print 문: 시스템의 표준 출력 장치(화면) 로만 데이터를 보내는 단방향 제어 명령이다. 별도의 출력 대상 장치 번호를 기술할 필요가 없어, 단순 연산 값을 신속하게 모니터링할 때 간단하게 사용한다.
write 문: print 문과 달리 출력할 장치와 파일 등을 직접 지정하여 출력할 수 있는 명령이다. 만약 이 장치 식별란에 고유 숫자 대신 별표 기호(*)를 기입하면 표준 출력 장치(화면)로 설정된다.
아래 두 출력 구문은 정확하게 동일한 방식으로 화면에 데이터를 출력한다.
print *, "hello"
write(*, *) "hello"
여기서 write(*, *) 구문의 첫 번째 인자 (*) 는 데이터를 전송할 목적 장치로서 여기서는 표준 출력(화면) 장치를 의미하고, 두 번째 인자 (*) 는 하드웨어가 읽어 들일 데이터의 서식으로서 여기서는 기본(default) 서식 체계를 그대로 적용할 것을 명령한다.
print *, 와 write(*,*) 출력 비교다음은 프로그램 내부 메모리에 탑재된 정수 변수를 각각 print 단축문과 write 범용문을 결합하여 화면에 동시 전송하는 코드이다.
%%writefile write_print.f90
program write_print
implicit none
integer :: n
n = 42
print *, "via print:", n
write(*, *) "via write:", n
end program write_print
실행 결과:
via print: 42
via write: 42
실행 결과가 정밀하게 동기화되어 출력되었다.
[!note] 현대적 시스템 자원 관리: 동적 장치 번호
newunit과거 레거시 FORTRAN 표준에서는 파일 제어 시 개발자가 임의의 정수(예:
open(6, ...))를 장치 번호(unit number)로 직접 지정하여 사용했다. 그러나 이 방식은 거대 수치 해석 모델 융합 시 다른 서브루틴이나 라이브러리가 사용하는 고유 번호와 우연히 겹쳐 자원이 충돌하는 중대한 설계적 결함을 안고 있다. 현대 Fortran의newunit지시자는 시스템 내부에서 현재 전혀 사용하지 않는 빈 장치 번호를 운영체제로부터 동적으로 선택받아 정수형 변수u에 자동으로 할당한다. 따라서, 개발자는 번호 충돌 가능성에 대해 신경 쓸 필요가 없으며, 컴파일러가 장치 번호의 할당과 제거를 알아서 관리하기 때문에 프로그램의 무결성을 확보할 수 있다. 자세한 내용은 14장에서 다룬다.
[!important] 6.3 서식과 편집 기술자
임의로 출력 레이아웃을 결정하는 목록 지정 방식에서 벗어나, 개발자가 출력 모양을 제어하기 위해서는 명시적인 서식(format) 규격을 선언한다.
변수 목록을 서식 인터페이스로 전달하는 표준 제어 구문 규격은 다음과 같다. 서식은 기본적으로 작은 따옴표('') 로 감싼 형식을 취하며, 그 내부의 괄호 안에 출력하고자 하는 표현 방식인 편집 기술자(edit descriptor)들을 쉼표로 나열한다.
write 범용 서식 구문write(장치, '(편집기술자1, 편집기술자2, ...)') 출력목록
장치 식별자 자리에는 표준 화면 출력을 나타내는 별표(*) 기호나 고유 장치 번호를 배정할 수 있다.
print 단축 서식 구문fortran
print '(편집기술자1, 편집기술자2, ...)'출력목록
별도의 장치 제어 인수 없이 오직 화면으로만 서식화된 레이아웃 문자열을 출력한다.
가장 우측에 배치된 출력 목록의 개별 변수 값들과 괄호 안의 편집 기술자들은 좌측에서 우측 방향으로 순차적으로 일대일 매핑(mapping)되어 지정된 하드웨어로 출력된다.
각 데이터 타입에 매칭되는 핵심 편집 기술자들은 다음과 같이 정리 된다. 같은 기술자를 여러 번 쓸 때는 앞에 반복 횟수를 붙인다(3I5 는 I5, I5, I5 와 같다). 또한 큰 따옴표로 글자를 감싸면 그대로 출력되는 리터럴 문자열이 된다( '(F8.3, " m/s")').
Iw: 정수형 데이터를 표현
전체 출력 폭을 w칸으로 고정하며, 만약 실제 차지하는 자릿수보다 설정한 너비 w가 더 클 경우 남는 공간은 좌측에 공백(␣)을 채워진다. 예를 들어, I5 기술자에 정수 42를 매핑하면 좌측에 공백 세 칸을 두고 ␣␣␣42로 정렬된다.
I0: 기술자는 가변 너비 정수형 출력을 수행
데이터의 크기에 정확히 맞는 최소한의 필요한 자릿수 만큼만 폭을 자동으로 조정하여 출력하므로, 불필요한 앞 공간의 공백을 없애고 정수 값(42)만을 표현한다.
Fw.d: 실수를 고정소수점 형태로 변환
총 너비 w칸을 확보한 상태에서 소수점 이하 자릿수d를 고정하여 정밀도를 통제한다. 예를 들어, F8.3 기술자에 원주율 근사치인 3.142를 전달하면, 전체 8칸의 영역을 배정한 뒤 소수점 아래 세 자리까지 정렬하고 남는 전면 영역은 공백으로 채워 ␣␣␣3.142 형태로 출력된다.
Ew.d: 실수를 일반 지수 표기법(exponential notation)으로 변환
전체 너비 w칸 속에 소수점 아래 자릿수를 d자리로 한정하여 0.xxxxE+xx 포맷으로 표현한다. 예를 들어, E12.4 기술자에 아보가드로 상수 계열의 숫자를 매핑하면 ␣␣0.6022E+24와 같이 출력된다.
ESw.d: 과학적 표기법(scientific notation)으로 변환
소수점 왼쪽에 가수의 절대값 크기를 반드시 $1$ 이상, $10$ 미만의 한 자리 정수로 하는 과학적 규격을 지원한다. 예를 들어, ES12.4 로 쓰면 수치는 ␣␣6.0220E+23과 같은 양식의 형태로 조정된다.
A: 문자열 데이터를 다루는 가변식 변환
문자열이 지닌 본래 길이를 그대로 단 1바이트의 공백 낭비도 없이 원본 텍스트 자체를 출력한다. 예를 들어, 입력 문제열이 ␣␣hello 또는 hello␣ 등의 형태라도 공백 없이 hello가 출력된다.
Aw: 고정 폭 문자열 제어
문자 출력을 위한 전용 격자 칸을 w 크기만큼 확보하도록 지시한다. 예를 들어, A10 기술자에 "hello"를 전송하면 지정된 10칸 중 우측에 텍스트를 두고 좌측 남는 영역을 공백으로 채워 ␣␣␣␣␣hello 형태로 출력한다.
nX: 하드웨어 포인터를 전진시키는 공백 생성기
출력 목록의 값과 직접 매핑되지 않고, 지정된 정수 n만큼 데이터 기록 커서를 우측으로 건너뛰어 빈칸을 형성한다. 예를 들어, 3X 지시자는 포인터를 $3\text{바이트}$ 뒤로 이동시켜 출력 결과물에 ␣␣␣ 형태의 세 칸짜리 여백을 만든다.
/ (슬래시): 줄 바꿈 실행
현재 기록 중인 출력 레코드(줄, 행)를 강제로 끝내고 줄 바꿈(new record)하여, 후속 데이터를 다음 행의 맨 첫 머리부터 다시 기록한다.
[!warning] [흔한 실수] 실수형 변수를 정수용 기술자로 매핑 실수형 변수를 정수용 편집 기술자인
I를 매핑하려고 하면, 빌드(compile) 단계에서는 문법적 하자 없어 정상 통과되지만, 실행(run-time) 시 강제 종료된다.
fortran real :: x = 3.14 print '(I5)', x실행 결과:
Fortran runtime error: Expected INTEGER for item 1 in formatted transfer, got REAL실수는 반드시
F,E,ES기술자와 정렬하고, 정수형은 정확히I기술자와 짝을 맞추어 매핑해야 한다.
데이터를 텍스트 형식으로 일차 출력할 때, 각 변수들의 값이 세로 열 기준으로 정렬되어 있어야만 가독성을 확보할 수 있다.
본 예제는 문자열(열 머리글)을 다루는 A 기술자와 정수 및 실수를 다루는 I, F 기술자의 출력 폭(width)을 일치시킴으로써 세로 격자 줄이 정확히 맞아 떨어지는 고정 폭 표(formatted table) 형태로 출력하는 것이다.
서로 다른 데이터 타입의 숫자를 출력 폭(4, 10, 10, 12)을 맞추어 열을 정렬하는 방법을 잘 익혀두자.
%%writefile formatted_table.f90
program formatted_table
implicit none
integer :: i
real :: x, sq, cube
print '(A4, A10, A10, A12)', "i", "x", "x^2", "x^3"
do i = 1, 5
x = real(i) * 0.5
sq = x**2
cube = x**3
print '(I4, F10.3, F10.3, F12.4)', i, x, sq, cube
end do
end program formatted_table
컴파일·실행:
!gfortran -O2 -std=f2018 -Wall formatted_table.f90 -o formatted_table
!./formatted_table
실행 결과:
i x x^2 x^3
1 0.500 0.250 0.1250
2 1.000 1.000 1.0000
3 1.500 2.250 3.3750
4 2.000 4.000 8.0000
5 2.500 6.250 15.6250
세로줄이 정확하게 일직선으로 정렬되었다. 머리글(헤더)에 적용된 문자열 폭 규격인 A4, A10, A10, A12와 내부 데이터 루프에 부여된 수치 폭 파라미터인 I4, F10.3, F10.3, F12.4가 동일한 공간(총 36칸: $4 + 10 + 10 + 12$)을 차지하도록 매핑하여 구현하였다.
Fortran의 문자 및 숫자 편집 기술자는 기본적으로 지정된 전체 칸 너비 안에서 우측을 기준선으로 정렬한다(right-alignment). 따라서 머리글의 문자열도, 데이터 행의 숫자들도 각 열의 우측 경계면을 기준으로 정렬되므로 데이터의 자릿수 변화와 상관없이 깔끔한 표 형태를 유지한다.
F10.3으로 선언된 열은 소수점 세 번째 자리까지, 마지막 F12.4는 소수점 네 번째 자리까지 규정함으로써, 부동소수점 연산 시 발생하는 꼬리 숫자를 제거하고 균일하게 출력한다.
기상학, 천문학, 또는 분자 물리 연산 시 마주하는 극단적으로 크거나 작은 수치 데이터(예: 아보가드로 수, 플랑크 상수, 대기 물질 농도 등)는 고정소수점 방식인 F 기술자로 출력하면 무수히 많은 $0$이 나열되어 가독성이 떨어진다.
부동소수점을 효과적으로 압축하여 표현하는 E (일반 지수 표기법) 기술자와 학술 논문 및 보고서에서 표준 규격으로 채택하고 있는 ES (과학적 표기법) 기술자의 기계적 변환 결과 차이를 이 예제로 비교해 보자.
%%writefile sci_notation.f90
program sci_notation
implicit none
real :: avogadro
avogadro = 6.022e23
print '(A, ES12.4)', "N_A = ", avogadro
print '(A, E12.4)', "N_A = ", avogadro
end program sci_notation
컴파일·실행:
!gfortran -O2 -std=f2018 -Wall sci_notation.f90 -o sci_notation
!./sci_notation
실행 결과:
N_A = 6.0220E+23
N_A = 0.6022E+24
같은 값이지만 ES 는 가수를 6.0220(1~10 사이)으로, E 는 0.6022(0~1 사이)로 적는다. 물리량을 보여 줄 때는 보통 ES 가 자연스럽다.
첫 번째 출력 라인인 ES12.4를 거친 결과는 6.0220E+23으로 나타난다. 과학적 표기 기술자인 ES는 소수점 왼쪽의 가수를 반드시 $1.0$ 이상, $10.0$ 미만의 범위(즉, 단 한 자리의 정수)로 정규화한다. 학술 보고서의 수치 레이아웃과 일치하는 가독성을 가진다.
두 번째 출력 라인인 E12.4를 거친 결과는 0.6022E+24로 변환된다. 일반 지수 표기 기술자인 E는 소수점 왼쪽을 무조건 $0$으로 만들고, 소수점 바로 첫째 자리부터 유효숫자가 정렬되도록(가수가 $0.1$ 이상, $1.0$ 미만이 되도록) 정규화한다. 이 방식은 부동소수점을 일괄 표준화할 때 주로 사용된다.
[!important] 6.4 데이터의 영구 저장: csv 파일 생성
가공된 데이터를 물리 디스크 상에 보존하고 외부 분석 프레임워크와 유기적으로 연동하기 위해서는 파일 입출력(File I/O) 과정이 필요하다.
연산을 수행하여 범용 파일 포맷인 csv (comma-separated values, 쉼표로 구분된 값) 형태로 디스크에 저장하면, 시각화 등의 후처리도 가능하다.
물리 디스크 상에 빈 파일을 생성하고 데이터를 저장하는 과정은 전산학적으로 다음의 세 단계 순서를 거친다.
open(newunit=u, file="data.csv", status="replace", action="write")
write(u, '(서식)') 출력목록
close(u)
각 단계별 작동 방식과 옵션 지시자들의 역할은 다음과 같다.
1단계 - 동적 장치 번호 할당 및 파일 개설 (open): 현대 Fortran 표준은 newunit=u 지시자를 가동하여 사용 가능한 장치 번호를 시스템 내부에서 자동 선택하여 정수형 변수 u에 할당한다.
status="replace" 옵션은 경로에 동일한 이름의 파일이 이미 있을 경우 기존 데이터를 지우고 파일 자체를 새로 생성(overwrite)한다.
action="write" 옵션은 해당 파일을 오직 '쓰기 전용'으로만 연결하여, 하드웨어 장치 가동 중 발생할 수 있는 원치 않는 동시 읽기 간섭 등의 쓰기 충돌을 예방한다.
2단계 - 파일 표적 출력 전송 (write): 화면 출력을 의미하는 별표(*) 대신, open 단계에서 동적으로 할당받은 장치 변수 u를 전면에 배치하여 데이터 패킷이 화면이 아닌 실제 디렉토리 내 지정된 디바이스 영역으로 안전하게 유통되도록 흐름을 완전히 전환한다.
3단계 - 장치 연결 폐쇄 및 메모리 플러시 (close): 모든 기록 작업이 성공적으로 완료되면 배정되어 구동 중인 장치 변수 u를 완전하게 닫고, 파일 기록이 완결된다.
중력장 내에서 특정 초기 속도와 발사각으로 발사된 물체의 포물선 운동은 물리학의 고전적이고 기초적인 시뮬레이션 모델이다.
본 예제는 투사체 물리 방정식을 설계하고, 시간에 따른 수평·수직 위치 좌표 $(x, y)$를 시계열 데이터인 csv 파일로 저장한 후 파이썬으로 시각화한다. 각도 단위 변환(degree $\rightarrow$ radian) 및 비행 시간 분석을 거쳐 총 41개의 이산 데이터 쌍을 파일로 저장하는 과정을 포함한다.
%%writefile projectile.f90
program projectile
implicit none
integer, parameter :: n = 40
real, parameter :: g = 9.81, v0 = 30.0, deg = 45.0
real, parameter :: pi = 3.14159265
real :: theta, t, dt, x, y, t_flight
integer :: i, u
theta = deg * pi / 180.0
t_flight = 2.0 * v0 * sin(theta) / g
dt = t_flight / real(n)
open(newunit=u, file="trajectory.csv", status="replace", action="write")
write(u, '(A)') "x,y"
do i = 0, n
t = real(i) * dt
x = v0 * cos(theta) * t
y = v0 * sin(theta) * t - 0.5 * g * t**2
write(u, '(F10.4, ",", F10.4)') x, y
end do
close(u)
print *, "trajectory.csv written, points =", n + 1
end program projectile
컴파일·실행:
!gfortran -O2 -std=f2018 -Wall projectile.f90 -o projectile
!./projectile
실행 결과:
trajectory.csv written, points = 41
!head trajectory.csv 명령어를 실행하여 출력 파일의 구조를 확인한다.
x,y
0.0000, 0.0000
2.2936, 2.2362
4.5872, 4.3578
...
F10.4 지시자로 인해 숫자 왼쪽에 공백 여백들이 일정하게 정상적으로 들어가 있다.
이제 "trajectory.csv" 의 좌표 정보를 읽어 파이썬으로 시각화해 보자. csv 파일 헤더(x,y)는 데이터가 아니므로 next(reader) 로 한 줄 건너뛰고 2 행부터 읽어 들인다.
import csv
import matplotlib.pyplot as plt
xs, ys = [], []
with open("trajectory.csv") as f:
reader = csv.reader(f)
next(reader) # skip header row "x,y"
for row in reader:
xs.append(float(row[0]))
ys.append(float(row[1]))
# 1. 그림 크기를 설정한다 (가로 크기는 지침에 따라 항상 10으로 고정)
plt.figure(figsize=(6, 4))
# 2. 지정된 캔버스 위에 그래프를 생성한다
plt.scatter(xs, ys, s=20, color="tab:red")
plt.xlabel("x (m)")
plt.ylabel("y (m)")
plt.title("Projectile trajectory (Fortran -> Python)")
plt.grid(True)
plt.savefig("trajectory.png", dpi=120)
plt.show()
![[Pasted image 20260715124844.png]]
출력된 그래프를 보자. 각 점은 각 시각 마다의 물체 위치이며, 전체가 모이면 포물선 궤적이 된다.
완벽한 좌우 대칭성 구현: 발사 지점인 원점 $(0,0)$에서 최고점에 도달하기까지의 수평 변위와, 최고점에서 최종 낙하 지점까지의 하강 궤적이 대칭형 포물선(parabolic path)을 그린다. 최고 높이는 약 $11.47\text{ m}$ 부근이고, 수평 비행 거리는 약 $91.74\text{ m}$ 지점에 착륙한다.
등시간 간격 격자점의 분해: 각 점은 동일 비행 시간 격자 간격($dt \approx 0.108\text{초}$)을 가진다. 투사체의 수평 방향 속도는 등속도 운동($v_x = v_0 \cos\theta$)을 따르므로 가로축을 따라 일정한 간격으로 점들이 배치되었다.
[!important] 6.5 NaN·Inf와 ieee_arithmetic
실제 수치 시뮬레이션 도중에는 수학적 한계를 벗어나는 연산이 빈번하게 발생한다. 대표적으로 분모가 $0$에 수렴하는 나눗셈 연산을 집행하거나, 음수의 로그(log) 및 제곱근(sqrt)을 구하려 하는 경우다.
이런 예외적인 연산 환경을 제어하지 않으면 하드웨어 시스템은 비정상 수치인 Inf와 NaN이 csv 데이터에 저장되면 후처리에 에러가 발생할 수 있다. 따라서 본 절에서는 이런 특수 예외 값의 특성을 분석하고 표준 모듈을 통해 제거하는 기술을 익힌다.
부동소수점 하드웨어 연산 표준(IEEE 754) 체계에서는 통상적인 실수 범주에 속하지 않는 값들을 특별한 상태로 규정하여 처리한다.
Inf (Infinity, 무한대): $0$이 아닌 유한한 실숫값을 정확히 $0.0$으로 나누었을 때 나타난다. 수치 연산 방향에 따라 양의 무한대(Inf) 혹은 음의 무한대(-Inf)의 극한 상태를 지닌다.
NaN (Not a Number, 숫자가 아님): 분모와 분자가 모두 $0$인 부정형 연산($0.0 / 0.0$)이나 음수의 제곱근($\sqrt{-1.0}$), 혹은 음수 영역에서의 자연로그($\ln(-5.0)$)와 같이 수학적으로 정의할 수 없을 때 부여되는 상태이다.
아래 코드는, 컴파일 단계에서 오류가 발생하지 않도록 미리 대비하기 위해 외부 입력을 통해 분모 값으로 0.0을 전달 받아 NaN 과 Inf 상태 값을 의도적으로 생성한다.
%%writefile div_zero.f90
program div_zero
implicit none
real :: a, zero
a = 7.7
read *, zero
print *, "a / zero =", a / zero
print *, "-a / zero =", -a / zero
print *, "zero / zero =", zero / zero
end program div_zero
아래와 같이 컴파일하고 리눅스 파이프를 통하여 분모에 $0.0$를 주입한다.
!gfortran -O2 -std=f2018 -Wall div_zero.f90 -o div_zero
!echo "0.0" | ./div_zero
실행 결과:
a / zero = Infinity
-a / zero = -Infinity
zero / zero = NaN
위 예제를 통해 NaN 과 Inf, -Inf 가 발생하는 수학적 조건을 명확하게 확인할 수 있다.
ieee_arithmetic 내장 모듈을 가용한 수치 필터링터미널에 출력된 오류 정보는 육안으로 판별할 수 있으나, 연산 과정 도중에 정상 데이터만 추출하여 저장하기 위해서는 프로그램 스스로 예외 유무를 식별할 수 있어야 한다.
현대 Fortran 표준은 수치 예외 상태를 감지할 수 있는 내장 모듈인 use, intrinsic :: ieee_arithmetic을 제공한다.
ieee_is_finite(x) 함수는 실수 변수 x가 무한대(Inf)나 숫자가 아님(NaN) 상태가 아닌 유한한 '정상 수치' 상태일 때 참(.true.)을 반환한다.
ieee_is_nan(x) 함수는 변수 x가 정상적인 수치 계산이 불가한 NaN 상태일 때 참(.true.)을 반환한다.
ieee_value() 함수는 시스템 아키텍처에 종속되지 않고 안전한 방식으로 예외 검증용 물리 기준값(ieee_positive_inf, ieee_quiet_nan)을 직접 변수에 주입하고자 할 때 사용되는 표준 도구다.
%%writefile ieee_check.f90
program ieee_check
use, intrinsic :: ieee_arithmetic
implicit none
real :: inf_val, nan_val, normal
normal = 7.7
inf_val = ieee_value(0.0, ieee_positive_inf)
nan_val = ieee_value(0.0, ieee_quiet_nan)
print *, "is_nan(nan), is_nan(normal):", ieee_is_nan(nan_val), ieee_is_nan(normal)
print *, "is_finite(normal), is_finite(inf):", ieee_is_finite(normal), &
ieee_is_finite(inf_val)
print *, "nan == nan:", nan_val == nan_val
end program ieee_check
컴파일·실행:
!gfortran -O2 -std=f2018 -Wall ieee_check.f90 -o ieee_check
!./ieee_check
실행 결과:
is_nan(nan), is_nan(normal): T F
is_finite(normal), is_finite(inf): T F
nan == nan: F
최종 출력 구문에서 nan_val == nan_val이 거짓(.false.)으로 판정된 것은 코드 설계에서 매우 중요한 의미를 가진다.
IEEE 754 표준에 따르면, NaN은 그 어떤 수치와도 대등하지 않으며, 심지어 자기 자신과 비교해도 논리적 동등성이 성립하지 않는다. 연산 결과의 정상 여부를 판단하기 위해 if (x == nan_val)이나 if (x /= nan_val) 같은 일반 비교문을 사용하면 컴파일러는 무조건 거짓(.false.)으로 처리한다. 따라서, NaN을 판별할 때는 반드시 표준 함수인 ieee_is_nan(x)를 활용해 예외 상태를 필터링해야 한다.
이런 감지 장치를 구축해 두면, 시뮬레이터 등이 연산 도중 분모가 0에 가까워지거나 0이 되어 연산 불가능한 상태에 빠져 NaN이나 Inf 가 나오더라도 csv 에 저장되는 것을 원천적으로 막을 수 있다.
[!warning] [흔한 실수]
NaN판독 시 동등성 비교 연산자(==) 오용NaN을x == nan으로 검사하면 항상 거짓이라 검출되지 않는다.NaN은 자기 자신과도 같지 않기 때문이다.NaN여부는 반드시ieee_is_nan(x)로, 무한대 포함 비정상 여부는.not. ieee_is_finite(x)로 검사한다.
[!important] 오류 학습
status="new" vs "replace" 두 번 실행하면 오류 발생Fortran의 open 문에서 파일 생성 및 연결 규칙을 수립할 때, 직관적인 이름 때문에 status="new"를 선택하는 실수를 범하기 쉽다.
그러나 이 지시자는 하드웨어 및 운영체제(OS)의 파일 시스템과 긴밀히 상호작용하며 독특한 방어 잠금 기능을 수행하므로, 그 기계적 거동을 정확히 이해하고 상황에 맞춰 알맞은 상태 스위치를 장착해야 한다.
status="new" 지정 후 연속 실행 시의 디바이스 충돌코랩이나 개발 서버 환경에서 동일한 연산 셀을 두 번 이상 연속 가동하는 시나리오를 가정하여 아래 소스코드를 실행해 본다. 기존 주석과 구조를 단 하나도 훼손하지 않고 완벽히 보존한 상태에서, 두 번째 진입 시점에 런타임이 어떻게 중단되는지 그 궤적을 확인한다.
%%writefile bad_status.f90
program bad_status
implicit none
integer :: u
open(newunit=u, file="out.csv", status="new", action="write")
write(u, '(A)') "x,y"
write(u, '(F4.1, ",", F4.1)') 1.0, 2.0
close(u)
print *, "out.csv written"
end program bad_status
gfortran을 사용해 빌드를 수행한 뒤, 프로그램을 연속으로 2회 호출하여 파일 시스템의 방어 기작을 모니터링해 보자.
!gfortran -O2 -std=f2018 -Wall bad_status.f90 -o bad_status
# 1차 실행 (성공)
!./bad_status
1차 실행 결과:
out.csv written
첫 번째 실행 단계에서는 지정한 경로 상에 "out.csv"라는 파일이 존재하지 않았으므로 정상적으로 디스크 공간에 파일이 안착되며 완료 메시지를 출력한다.
그러나 이 상태에서 실행을 한 번 더 해 보자.
# 2차 실행 (실패 및 충돌 발생)
!./bad_status
2차 실행 결과:
At line 5 of file bad_status.f90
Fortran runtime error: Cannot open file 'out.csv': File exists
status="new"의 전산학적 엄격성: 물리 입출력 규격에서 "new"라는 인수는 단순히 "새로 생성한다"는 뜻에 그치지 않고, 반드시 타깃 파일이 존재하지 않아야 하며, 만약 이미 파일이 있다면 기존 데이터의 안전을 위해 쓰기 작업을 즉각 중단하고 예외를 호출한다.
시뮬레이션 반복 연산 시의 함정:** 1차 실행 과정에서 이미 "out.csv"가 디스크 영역에 안전하게 저장되었다. 따라서 2차 가동 시점에 시스템은 "이미 파일이 존재하고 있음(file exists)"을 감지하고 규칙 위반으로 판단하여 이후 로직을 완전히 차단하 프로그램을 강제로 종료(crash)시킨다.
연구실이나 테스트 벤치 환경에서는 매개변수나 격자 조건을 조금씩 수정하며 동일 소스 코드를 수십 번 반복 호출하여 결과 분석을 진행하므로, 이 옵션을 고집하면 매 실행 전에 매번 수동으로 디스크 내 파일을 지워야 하는 극심한 불편을 겪게 된다.
status="replace" 지시자를 통한 덮어쓰기매 연산 주기마다 계산 결과 데이터를 안전하게 최신화하며 몇 번이고 반복 재실행이 가능하도록 보장하려면, 덮어쓰기 규칙인 status="replace"를 아래 문법 규칙으로 사용한다.
open(newunit=u, file="out.csv", status="replace", action="write")
status="replace"의 물리적 작동 흐름: 지정 경로 상에 타깃 파일이 이미 존재한다면, 기존 데이터를 소거(truncate)하여 빈 공간으로 만든 뒤 새 파일을 쓴다. 만약 파일이 존재하지 않는 경우라면 "new"와 완전히 동일하게 파일 자체를 새로 생성하여 데이터를 기록한다.
결과적으로 덮어쓰기가 잦은 출력인 경우, status="replace"를 기본으로 사용하는 것이 프로그램의 신뢰성을 확보하는 요령이다. 실수로 데이터를 덮어쓰는 사고를 원천 차단하고 고유한 새 파일 생성을 해야하는 보안 설계에서만 "new" 옵션을 선별 적용해야 한다.
[!important] 요약
read *, / print *, / write(*, *). 서식을 * 에 맡기므로 간편하지만 칸·자릿수를 통제할 수 없다.print 는 항상 표준 출력, read 는 입력, write(장치, 서식) 는 장치를 골라 출력. 표준 입출력 장치는 * 로 표기한다.print '(편집기술자, ...)', 목록. 출력 모양을 직접 통제한다.Iw·I0, 실수 Fw.d, 지수 Ew.d·ESw.d, 문자 A·Aw, 공백 nX, 줄바꿈 /. 반복은 nIw, 리터럴은 "글자".open(newunit=u, file=..., status="replace", action="write") → write(u, ...) → close(u)."," 를 끼워 값들을 쉼표로 구분하고, 첫 줄에 머리글을 적는다. Python은 csv.reader 로 읽고 next(reader) 로 머리글을 건너뛴다.x/0.0 → Inf, 0.0/0.0·음수의 log·sqrt(-1.0) → NaN. ieee_arithmetic 의 ieee_is_finite·ieee_is_nan 으로 검출하며, NaN 은 == 비교로 판별할 수 없다.* 출력, 서식·자료형 불일치 → 실행 중 오류, Colab에서 입력 없는 read → 멈춤, NaN 을 == 로 검사 → 항상 거짓, status="new" 로 연 파일 재실행 → File exists 오류(replace 로 해결).[!important] 연습 문제
I0, I5, I8 세 가지 너비로 각각 한 줄씩 출력하라. 세 출력의 칸 너비 차이를 눈으로 확인하라.3.14159265 를 F10.2, F10.5, ES14.6 으로 각각 출력하라.c 를 입력받아 화씨온도 f = c * 9.0 / 5.0 + 32.0 를 계산하고, "25.0 C = 77.0 F" 같은 형태가 되도록 리터럴 문자열과 F 기술자를 섞어 한 줄로 출력하라.F4.1 로 출력해 * 가 나오는 값을 하나 찾고, 너비를 늘려 정상 출력되게 고쳐라.a / b 를 출력하고, 그 결과가 유한한 값인지 여부를 ieee_is_finite 로 함께 출력하라. b 에 0을 주어 결과를 확인하라.t 를 0부터 1까지 0.1 간격으로 바꾸며 t 와 exp(-t) 를 decay.csv(머리글 t,value)로 출력하라. Python으로 선그래프를 그려라."3 x 1 = 3" 형태로 9줄 출력하라. I 기술자와 리터럴 문자열을 쓰라.sin, cos 값을 trig.csv(머리글 deg,sin,cos)로 출력하라. Python으로 두 곡선을 한 그림에 겹쳐 그려라.F12.4)로 칸을 맞춰 출력하라.