Devin.KR

Fortran · 기본

계산으로 배우는 현대 Fortran

조건과 반복 - 이름 붙은 do 루프

if·else if, select case 와 범위, do 카운터 루프·간격, do while, exit·cycle, 이름 붙은 루프로 바깥 루프 빠져나가기

개발자KR · 원고 갱신

이 장에서 배우는 것

지금까지 만든 프로그램은 위에서 아래로 한 줄씩 실행되고 끝났다. 관측 자료를 다루려면 두 가지 능력이 더 필요하다. 하나는 값에 따라 실행할 문장을 고르는 능력이고, 다른 하나는 같은 계산을 여러 번 되풀이하는 능력이다. 앞 장에서 정수 나눗셈과 실수 오차를 다뤘으니, 이 장에서는 그 값을 비교하고 세는 방법으로 넘어간다. 하루 24시간의 기온과 강수량을 분류하고 집계하는 작은 프로그램으로 이 두 가지를 익힌다.

  • if, else if, else 로 기온을 구간별로 분류한다.
  • select case 로 정수 값을 낱개·범위·열린 범위로 나눈다.
  • do 카운터 루프의 시작값, 끝값, 간격(step)을 정하고 반복 횟수를 미리 계산한다.
  • do while, exit, cycle 로 조건에 따라 반복을 이어가거나 건너뛰거나 끝낸다.
  • 이름 붙은 루프로 이중 루프를 한 번에 빠져나간다.

문제 상황

작은 관측소에서 사흘 치 시간별 자료가 쌓였다. 담당자가 확인하려는 것은 대략 다음과 같다.

  • 하루 중 추운 시간, 서늘한 시간, 온화한 시간, 더운 시간이 각각 몇 시간인가.
  • 비는 시간당 몇 mm 부터 강하다고 볼 것이며, 날마다 약한 비·보통 비·강한 비 시간이 몇 시간씩인가.
  • 누적 강수량이 20 mm 에 처음 도달한 시각은 언제인가.
  • 사흘을 통틀어 처음으로 강한 비가 내린 날과 시각은 언제인가. 찾았으면 더 볼 필요가 없다.

손으로 하면 표를 눈으로 훑으며 줄을 긋는 일이다. 컴퓨터에서는 "시간을 하나씩 돌며, 값을 보고, 갈래를 고른다"는 구조로 옮긴다. 이 구조가 조건문과 반복문이다. 아직 배열을 배우지 않았으므로 자료는 파일에서 읽지 않고 간단한 식으로 만든다. 기온은 14시에 가장 높고 양쪽으로 시간당 1도씩 내려가는 모양이며, 비는 16시를 중심으로 7시간 동안 내리고 날짜가 뒤일수록 세다.

조건 분기: if 와 select case

비교와 논리 연산

분기는 참 또는 거짓이 되는 식(논리식)이 있어야 한다. 비교에는 <, <=, >, >=, ==(같다), /=(다르다)를 쓴다. 여러 조건은 .and.(그리고), .or.(또는), .not.(아니다)로 묶는다. 점으로 감싼 이 모양이 Fortran 의 논리 연산자 표기다.

if, else if, else

가장 기본 형태는 다음과 같다. 조건 뒤에 then 을 붙이고 end if 로 닫는다.

if (temp < 10.0) then
   n_cold = n_cold + 1
else if (temp < 16.0) then
   n_cool = n_cool + 1
else
   n_warm = n_warm + 1
end if

조건은 위에서 아래로 차례대로 검사하고, 처음으로 참이 되는 갈래 하나만 실행한 뒤 end if 다음으로 간다. 두 번째 조건 temp < 16.0 에는 "10도 이상"이라는 말이 적혀 있지 않지만, 첫 조건이 이미 걸러냈으므로 10도 이상인 값만 여기까지 온다. 그래서 구간 경계는 위에서부터 한 번씩만 쓰면 된다. 조건 순서를 거꾸로 두면 앞의 조건이 뒤의 값을 모두 삼켜 버린다.

if 와 else if 체인은 위에서 아래로 검사하며 처음 참이 되는 조건에서 멈춘다.

조건이 하나이고 실행할 문장도 하나라면 한 줄 형태 if (조건) 문장 을 쓸 수 있다. 이 경우에는 then 과 end if 가 없다. 뒤에서 if (precip == 0) cycle 로 쓴다.

select case

한 값을 여러 후보와 견주는 경우에는 select case 가 읽기 쉽다. 이 장의 강수량 분류가 그런 경우다.

select case (precip)
case (0)
   n_none = n_none + 1
case (1:2)
   n_light = n_light + 1
case (3:9)
   n_mod = n_mod + 1
case (10:)
   n_heavy = n_heavy + 1
end select

case (1:2) 는 1 이상 2 이하를 뜻한다. 양 끝이 모두 포함된다. 한쪽을 비우면 열린 범위가 되어, (10:) 은 10 이상 전부이고 (:0) 은 0 이하 전부다. 쉼표로 낱개를 나열할 수도 있다(case (1, 3, 5)). 어느 case 에도 맞지 않는 값은 case default 가 받는다. case 범위끼리는 겹치면 안 되고, 겹치면 컴파일러가 오류로 알려 준다. if 체인과 달리 "먼저 맞는 것"을 고르는 방식이 아니라 서로 겹치지 않는 후보 중 하나를 고르는 방식이다.

select case 의 대상은 정수, 문자, 논리 값이어야 한다. 실수는 쓸 수 없다. 앞 장에서 본 것처럼 실수는 오차가 있어서 "정확히 같은 값"이라는 비교가 어울리지 않기 때문이다. 기온은 실수라서 if 체인으로, 강수량은 mm 단위 정수라서 select case 로 처리한다.

if 체인과 select case 는 판단 대상과 쓰임이 다르다
도구판단 대상어울리는 경우제약
if / else if모든 논리식실수 비교, 서로 다른 변수를 섞은 조건조건 순서가 결과에 영향을 준다
select case정수·문자·논리 한 값한 값을 낱개·범위로 나누는 분류실수는 쓸 수 없고 범위가 겹치면 안 된다

반복: do 루프의 여러 모양

카운터 루프와 간격

횟수를 알 때는 카운터 루프(counter loop)를 쓴다. do hour = 0, 23 은 변수 hour 를 0부터 23까지 1씩 늘리며 본문을 24번 실행한다. 세 번째 값을 주면 간격이 된다. do hour = 0, 23, 6 은 0, 6, 12, 18 이고, 음수 간격 do hour = 18, 0, -6 은 18, 12, 6, 0 으로 내려간다. 반복 횟수는 다음 식으로 정해지며 0 보다 작으면 한 번도 돌지 않는다.

횟수 = max( (끝값 - 시작값 + 간격) / 간격 , 0 )   (정수 나눗셈)

시작값 0, 끝값 23, 간격 6 이면 (23 + 6) / 6 = 4 로 네 번이다. 끝값 23 은 이 값에 도달해야 포함되는 상한일 뿐이어서 마지막 반복의 값은 18 이다. 그리고 루프가 끝난 뒤 카운터 변수에는 "다음에 쓰였을 값"이 남는다. do hour = 0, 23 이 끝나면 hour 는 24 다. 이 값에 기대는 코드는 읽기 어려우니, 필요한 값은 루프 안에서 따로 저장해 두는 편이 낫다.

do while

횟수를 모르고 조건이 참인 동안만 돌고 싶을 때는 do while 을 쓴다. 조건은 매 반복 앞에서 검사하므로 처음부터 거짓이면 본문은 한 번도 실행되지 않는다. 카운터 루프와 달리 변수를 올려 주는 일을 직접 해야 한다.

cum = 0
hour = 0
do while (cum < 20 .and. hour <= 23)
   cum = cum + max(0, 4 - abs(hour - 16)) * 3
   hour = hour + 1
end do

조건에 hour <= 23 을 함께 둔 이유는 20 mm 에 끝내 닿지 않는 자료가 와도 루프가 멈추도록 하는 안전장치다.

exit 와 cycle

두 문장은 본문 중간에서 흐름을 바꾼다. cycle 은 이번 반복의 남은 부분을 건너뛰고 다음 반복으로 간다. exit 는 반복 자체를 끝내고 end do 다음으로 간다. "비가 안 온 시간은 건너뛰고(cycle), 8 mm 를 넘으면 그만 본다(exit)"처럼 예외 처리를 앞에서 해치우면 본문이 깊게 들여쓰이지 않아 읽기 쉽다.

do 루프의 네 가지 형태
형태변수 값횟수용도
do h = 0, 230, 1, ..., 2324하루를 한 시간씩
do h = 0, 23, 60, 6, 12, 184간격을 두고 표본 추출
do h = 18, 0, -618, 12, 6, 04거꾸로 세기
do while (조건)직접 갱신미리 모름조건이 깨질 때까지

이름 붙은 루프로 바깥까지 나가기

루프 안에 루프가 있을 때 exit 는 자신이 속한 가장 안쪽 루프만 끝낸다. 사흘 치 자료에서 처음 나온 강한 비를 찾는다고 하자. 안쪽(시간) 루프에서 찾은 뒤 exit 만 쓰면 그 날의 시간 루프만 끝나고 바깥(날짜) 루프는 다음 날로 넘어가 버린다. 이때 루프에 이름을 붙인다(named loop).

search: do day = 1, 3
   do hour = 0, 23
      ...
      if (precip >= 10) exit search
   end do
end do search

이름은 이름: do 로 달고, 닫을 때도 end do 이름 으로 같은 이름을 쓴다. exit search 는 안쪽이 아니라 이름이 가리키는 루프를 끝낸다. cycle 이름 도 같은 방식으로 바깥 루프의 다음 반복으로 간다. 이름은 이중 이상의 루프에서 어느 루프를 가리키는지 분명하게 해 주므로, 안쪽 exit 만으로 충분한 경우에도 붙여 두면 읽는 사람에게 친절하다.

exit search 는 안쪽 시간 루프와 바깥 날짜 루프를 한 번에 끝내 65칸만 검사하고 멈춘다.

완성 코드

아래 프로그램은 위의 모든 요소를 한 파일에 담았다. 파일 이름은 main.f90 이다. 같은 강수량 식을 여러 곳에 적은 이유는 아직 식을 이름 붙여 재사용하는 방법을 다루지 않았기 때문이다. 이 반복은 뒤에서 함수로 정리한다.

main.f90

program control_flow
  implicit none
  character(len=*), parameter :: row_fmt = '(i0, 5(a, i0), a)'
  integer :: day, hour, precip, cum, day_sum
  integer :: n_cold, n_cool, n_mild, n_warm
  integer :: n_none, n_light, n_mod, n_heavy
  integer :: checked, found_day, found_hour
  real :: temp, temp_sum

  n_cold = 0
  n_cool = 0
  n_mild = 0
  n_warm = 0
  temp_sum = 0.0
  do hour = 0, 23
     temp = 20.0 - real(abs(hour - 14))
     temp_sum = temp_sum + temp
     if (temp < 10.0) then
        n_cold = n_cold + 1
     else if (temp < 16.0) then
        n_cool = n_cool + 1
     else if (temp < 20.0) then
        n_mild = n_mild + 1
     else
        n_warm = n_warm + 1
     end if
  end do
  print '(a, i0, a, i0, a, i0, a, i0, a)', '추움 ', n_cold, '시간, 서늘 ', n_cool, &
        '시간, 온화 ', n_mild, '시간, 더움 ', n_warm, '시간'
  print '(a, f5.2, a)', '1일차 평균 기온: ', temp_sum / 24.0, ' 도'

  print '(a)', '6시간 간격 기온:'
  do hour = 0, 23, 6
     temp = 20.0 - real(abs(hour - 14))
     print '(i2, a, f5.1, a)', hour, '시 ', temp, ' 도'
  end do

  do day = 1, 3
     n_none = 0
     n_light = 0
     n_mod = 0
     n_heavy = 0
     day_sum = 0
     do hour = 0, 23
        precip = max(0, 4 - abs(hour - 16)) * day
        day_sum = day_sum + precip
        select case (precip)
        case (0)
           n_none = n_none + 1
        case (1:2)
           n_light = n_light + 1
        case (3:9)
           n_mod = n_mod + 1
        case (10:)
           n_heavy = n_heavy + 1
        end select
     end do
     print row_fmt, day, '일차: 없음 ', n_none, ', 약함 ', n_light, ', 보통 ', n_mod, &
           ', 강함 ', n_heavy, ', 합계 ', day_sum, ' mm'
  end do

  cum = 0
  hour = 0
  do while (cum < 20 .and. hour <= 23)
     cum = cum + max(0, 4 - abs(hour - 16)) * 3
     hour = hour + 1
  end do
  print '(a, i0, a, i0, a)', '3일차 누적 20 mm 도달: ', hour - 1, '시 (누적 ', cum, ' mm)'

  print '(a)', '2일차 비가 온 시간 (8 mm 이상이면 중단):'
  day = 2
  do hour = 0, 23
     precip = max(0, 4 - abs(hour - 16)) * day
     if (precip == 0) cycle
     print '(i2, a, i2, a)', hour, '시 ', precip, ' mm'
     if (precip >= 8) exit
  end do

  checked = 0
  found_day = 0
  found_hour = -1
  search: do day = 1, 3
     do hour = 0, 23
        checked = checked + 1
        precip = max(0, 4 - abs(hour - 16)) * day
        if (precip >= 10) then
           found_day = day
           found_hour = hour
           exit search
        end if
     end do
  end do search
  print '(a, i0, a, i0, a, i0)', '첫 강한 비(10 mm 이상): ', found_day, '일차 ', &
        found_hour, '시, 검사한 시간 수 ', checked
end program control_flow

줄별 해설

  • row_fmt 는 이름 붙은 상수(parameter)로 둔 서식 문자열이다. 서식 i0 는 정수를 빈칸 없이 필요한 자릿수만큼 쓴다. 5(a, i0) 는 "문자열 하나, 정수 하나" 묶음을 다섯 번 되풀이하라는 뜻이다. 긴 서식을 한 번만 적으려고 따로 뺐다.
  • 선언부의 n_cold 등 개수 변수는 정수, temp 와 temp_sum 은 실수다. 개수는 정수로 세고 합은 실수로 모은다.
  • 첫 do hour = 0, 23 블록에서 기온은 20.0 - real(abs(hour - 14)) 다. 정수 abs(hour - 14) 를 real 로 실수로 바꿔 20.0 에서 뺀다. 14시에 20.0, 0시에 6.0 이 나온다.
  • if 체인의 경계는 10.0, 16.0, 20.0 이다. 마지막 else 는 20.0 이상을 받는다. 개수 변수는 반복 전에 0 으로 초기화했다. 초기화하지 않으면 값이 정해지지 않은 변수를 읽게 된다.
  • 평균은 합 330.0 을 24.0 으로 나눈 13.75 다. 서식 f5.2 는 전체 5칸, 소수 둘째 자리까지다.
  • do hour = 0, 23, 6 은 0, 6, 12, 18 시만 처리한다. 서식 i2 는 두 칸이므로 한 자리 숫자 앞에 공백이 하나 붙는다.
  • 세 번째 블록은 이중 루프다. 바깥이 날짜, 안쪽이 시간이다. max(0, 4 - abs(hour - 16)) * day 는 16시에 4 mm, 거기서 한 시간 멀어질 때마다 1 mm 씩 줄어 0 에서 멈추는 값에 날짜를 곱한 것이다. 날마다 개수 변수를 0 으로 되돌리는 위치가 안쪽 루프 앞이라는 점을 확인한다.
  • select case (precip) 는 정수 하나로 갈래를 고른다. (0), (1:2), (3:9), (10:) 은 서로 겹치지 않고 0 이상의 모든 정수를 덮는다.
  • do while 블록에서는 시간이 지날 때마다 3일차 강수량을 더한다. 누적이 20 이상이 되면 조건이 거짓이 되어 멈춘다. 멈춘 시점에 hour 는 이미 한 칸 더 올라가 있으므로 출력에서 hour - 1 을 쓴다.
  • if (precip == 0) cycle 로 비가 오지 않은 시간을 건너뛰고, if (precip >= 8) exit 로 8 mm 이상이 나오면 출력한 뒤 반복을 끝낸다. 출력한 다음에 검사하므로 8 mm 인 16시는 목록에 포함된다.
  • search: do day = 1, 3 이후가 이름 붙은 루프다. checked 는 검사한 시간 수를 세고, 찾으면 날짜와 시각을 저장한 뒤 exit search 로 두 루프를 한 번에 벗어난다. found_hour 의 처음 값 -1 은 "못 찾음"을 나타내는 표시로 쓰려고 둔 값이다.
  • 줄 끝의 & 는 문장이 다음 줄에 이어진다는 표시다. 문자열 안에서 끊지 않고 쉼표 뒤에서 끊었다.

실행 결과

$ gfortran -std=f2018 -Wall main.f90 -o control_flow
$ ./control_flow
추움 4시간, 서늘 11시간, 온화 8시간, 더움 1시간
1일차 평균 기온: 13.75 도
6시간 간격 기온:
 0시   6.0 도
 6시  12.0 도
12시  18.0 도
18시  16.0 도
1일차: 없음 17, 약함 4, 보통 3, 강함 0, 합계 16 mm
2일차: 없음 17, 약함 2, 보통 5, 강함 0, 합계 32 mm
3일차: 없음 17, 약함 0, 보통 6, 강함 1, 합계 48 mm
3일차 누적 20 mm 도달: 16시 (누적 30 mm)
2일차 비가 온 시간 (8 mm 이상이면 중단):
13시  2 mm
14시  4 mm
15시  6 mm
16시  8 mm
첫 강한 비(10 mm 이상): 3일차 16시, 검사한 시간 수 65

숫자가 맞는지 직접 확인해 볼 수 있다. 기온 분류의 4, 11, 8, 1 을 더하면 24 시간이다. 날마다 없음·약함·보통·강함의 합도 24 시간이다(17 + 4 + 3 + 0 처럼). 검사한 시간 수 65 는 24 + 24 + 17 이다. 3일차 0시부터 16시까지 17칸을 보고 멈췄다는 뜻이다. 실행 환경에 대한 자세한 옵션은 GNU Fortran 문서에서 확인할 수 있다.

실무에서 자주 틀리는 것

1. select case 에 실수를 넣는다

기온 같은 실수를 그대로 select case 에 넣으면 컴파일 단계에서 거부된다.

select case (temp)
case (:9.9)
   n_cold = n_cold + 1
end select

범위를 나누는 경계가 소수라면 if 체인을 쓴다. 정수 칸으로 충분하다면 반올림한 정수를 넣는다.

select case (nint(temp))
case (:9)
   n_cold = n_cold + 1
case (10:15)
   n_cool = n_cool + 1
case default
   n_warm = n_warm + 1
end select

nint 는 가장 가까운 정수로 반올림한다. 이 경우 9.6 은 10 이 되어 경계가 처음 의도와 달라질 수 있으니 반올림이 괜찮은지 먼저 따져 본다.

2. do while 안에서 조건 변수를 갱신하지 않는다

카운터 루프는 변수를 자동으로 올리지만 do while 은 그렇지 않다. 다음은 끝나지 않는다.

hour = 0
do while (hour <= 23)
   cum = cum + max(0, 4 - abs(hour - 16))
end do

hour 가 변하지 않아 조건이 영원히 참으로 남는다. 터미널에서는 Ctrl+C 로 멈춘다. 갱신 문장을 본문에 넣는다.

hour = 0
do while (hour <= 23)
   cum = cum + max(0, 4 - abs(hour - 16))
   hour = hour + 1
end do

3. 안쪽 루프의 exit 로 전체가 끝났다고 믿는다

날짜가 5일이라고 하고 다음 코드를 보자.

do day = 1, 5
   do hour = 0, 23
      precip = max(0, 4 - abs(hour - 16)) * day
      if (precip >= 10) exit
   end do
end do

3일차에서 찾아도 exit 는 시간 루프만 끝낸다. 4일차와 5일차를 계속 검사하고, 찾은 값을 따로 저장하지 않았다면 결과는 마지막 날의 상태가 된다. 루프에 이름을 붙이고 이름으로 나간다.

search: do day = 1, 5
   do hour = 0, 23
      precip = max(0, 4 - abs(hour - 16)) * day
      if (precip >= 10) exit search
   end do
end do search

4. 카운터 변수를 루프 안에서 바꾼다

"비가 안 오는 시간은 한 칸 더 건너뛰자"는 생각에 카운터를 직접 올리면 컴파일러가 오류로 막는다.

do hour = 0, 23
   if (precip == 0) hour = hour + 1
end do

카운터 루프의 변수는 루프가 관리하므로 본문에서 값을 대입할 수 없다. 건너뛰는 폭을 직접 정하고 싶다면 do while 로 바꾸거나, 간격을 인수로 주는 형태 do hour = 0, 23, 2 를 쓴다.

한눈에 보기

이 장의 문법과 기억할 점
문법하는 일기억할 점
if / else if / else / end if처음 참인 갈래 하나를 실행한다조건은 위에서 아래로 검사하며 순서가 결과를 바꾼다
if (조건) 문장한 문장만 조건부로 실행한다then 과 end if 를 쓰지 않는다
select case / case (a:b)정수·문자 값을 낱개·범위로 나눈다양 끝 포함, (10:) 은 10 이상, 실수는 불가
do i = s, e, k횟수가 정해진 반복끝난 뒤 변수는 한 칸 넘어간 값, 본문에서 대입 불가
do while (조건)조건이 참인 동안 반복조건 변수를 직접 갱신한다
cycle / exit이번 반복을 건너뛴다 / 반복을 끝낸다가장 안쪽 루프에만 적용된다
이름: do ... end do 이름exit·cycle 의 대상을 지정한다이름은 열 때와 닫을 때 모두 쓴다

연습 문제

  1. do hour = 20, 2, -6 은 몇 번 돌며 hour 는 어떤 값을 차례로 가지는가. 루프가 끝난 뒤 hour 의 값은 얼마인가.
  2. 시각 hour(0 이상 23 이하 정수)를 새벽(0~5), 오전(6~11), 오후(12~17), 밤(18~23)으로 나눠 이름을 출력하는 select case 를 써라. 범위 밖의 값도 처리하라.
  3. 본문의 do while 을 2일차 자료로 바꿔서, 누적 강수량이 20 mm 이상이 되는 첫 시각과 그때의 누적값을 구하는 코드를 써라. 결과는 얼마인가.
  4. 이름 붙은 루프를 사용해 1일차부터 3일차 중 하루 합계가 40 mm 이상인 첫 날을 찾는 코드를 써라. 하루 합계는 본문의 강수량 식으로 구한다.

정답과 해설

1번. 횟수는 (2 - 20 + (-6)) / (-6) = (-24) / (-6) = 4 이므로 네 번이다. 값은 20, 14, 8, 2 다. 다음 값 -4 는 끝값 2 보다 작아서 도달하지 못하고, 루프가 끝난 뒤 hour 는 -4 다.

2번.

select case (hour)
case (0:5)
   print '(a)', '새벽'
case (6:11)
   print '(a)', '오전'
case (12:17)
   print '(a)', '오후'
case (18:23)
   print '(a)', '밤'
case default
   print '(a)', '범위 밖'
end select

범위는 양 끝을 포함하므로 5 와 6 사이에 빈틈이 없다. case default 가 -1 이나 24 같은 값을 받는다.

3번.

cum = 0
hour = 0
do while (cum < 20 .and. hour <= 23)
   cum = cum + max(0, 4 - abs(hour - 16)) * 2
   hour = hour + 1
end do
print '(a, i0, a, i0, a)', '16시 확인: ', hour - 1, '시, 누적 ', cum, ' mm'

2일차의 시간별 값은 13시 2, 14시 4, 15시 6, 16시 8 이다. 누적은 2, 6, 12, 20 이 되고, 16시에 20 이 되어 조건 cum < 20 이 거짓이 된다. 결과는 16시, 누적 20 mm 이다. 본문의 3일차(누적 30)와 달리 정확히 20 이 되므로 경계를 포함하는지 비교 연산자로 확인하는 연습이 된다.

4번.

days: do day = 1, 3
   day_sum = 0
   do hour = 0, 23
      day_sum = day_sum + max(0, 4 - abs(hour - 16)) * day
   end do
   if (day_sum >= 40) then
      print '(a, i0, a, i0, a)', '40 mm 이상인 첫 날: ', day, '일차 (', day_sum, ' mm)'
      exit days
   end if
end do days

날마다 합계는 16, 32, 48 이므로 3일차에서 조건이 처음 참이 된다. 이 코드는 바깥 루프에서 exit 하므로 이름이 꼭 필요하지는 않지만, 안쪽 루프 뒤에 있는 if 라서 어느 루프를 끝내는지 이름이 분명하게 보여 준다. 합계를 내는 안쪽 루프를 앞에 두고 판단을 바깥 루프에서 한다는 순서도 눈여겨본다. 판단을 안쪽 루프에 넣으면 하루 합계가 완성되기 전에 검사하게 된다.

오탈자·오류 제보 비공개로 접수되어 원고 수정에 반영됩니다

이메일 등 개인정보는 받지 않습니다. 답변이 필요한 질문은 아래 댓글을 이용해 주세요.

READER FEEDBACK

질문·의견

내용에 관한 질문이나 더 나은 설명을 위한 의견을 남겨 주세요. 오탈자는 위의 제보 양식이 더 빨리 반영됩니다. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

댓글을 남기려면 로그인이 필요합니다.