Devin.KR

Fortran · 기본

계산으로 배우는 현대 Fortran

식과 내장 함수 - 정수 나눗셈과 실수 오차

산술 연산 우선순위와 **, 정수 나눗셈, 혼합 연산 형 변환, real·int·nint, 내장 함수(sqrt·abs·mod·max), 실수 비교와 허용 오차

개발자KR · 원고 갱신

이 장에서 배우는 것

앞 장에서는 변수를 선언하고 kind 로 정밀도를 고르는 방법을 배웠다. 변수에 값을 담았으니 이제 그 값으로 계산할 차례다. 이 장은 Fortran 의 산술식이 어떤 순서로 계산되고, 정수끼리 나누면 왜 소수점 아래가 사라지며, 정수와 실수가 한 식에 섞이면 무슨 일이 생기는지 다룬다. 마지막에는 실수 두 개가 "같은지" 확인하는 올바른 방법을 본다. 기상 관측소의 기온과 강수 기록을 계산하는 작은 프로그램이 예제다.

  • 연산자 우선순위(precedence)와 거듭제곱 ** 의 결합 방향을 알고, 괄호로 의도를 분명히 한다.
  • 정수 나눗셈(integer division)이 몫만 남긴다는 것과 mod, modulo 의 차이를 설명한다.
  • 정수와 실수가 섞인 식의 형 변환(type conversion) 규칙을 알고, real, int, nint 를 목적에 맞게 고른다.
  • sqrt, abs, mod, max 같은 내장 함수(intrinsic function)를 식 안에서 쓴다.
  • 실수를 == 로 비교하지 않고 허용 오차(tolerance)로 비교한다.

문제 상황

관측소 담당자가 하루 치 기록을 정리하는 프로그램을 짰다고 하자. 24시간 중 17시간 동안 비가 왔고, 이 비율을 백분율로 보고하려고 한다. 그런데 프로그램은 0 퍼센트를 출력한다. 세 시각의 기온 14.2, 15.8, 17.1 도로 평균을 냈더니 15.7 이 아니라 35.7 이 나온다. 마지막으로 0.1 과 0.2 를 더한 값이 0.3 과 같은지 물었더니 "같지 않다"는 답이 돌아온다.

세 가지 모두 컴파일러의 오류가 아니다. 언어가 정해 둔 규칙대로 계산한 결과다. 규칙을 모르면 계산식이 맞아 보이는데도 값이 틀리고, 그 값은 오류 메시지 없이 다음 계산으로 넘어간다. 이 장은 그 규칙을 하나씩 확인한다.

연산자 우선순위와 거듭제곱

Fortran 의 산술 연산자는 다섯 가지다. 더하기 +, 빼기 -, 곱하기 *, 나누기 /, 거듭제곱 **. 수학에서 배운 순서와 거의 같다. 거듭제곱이 가장 먼저, 그다음 곱하기와 나누기, 마지막에 더하기와 빼기를 계산한다. 우선순위가 같으면 왼쪽에서 오른쪽으로 계산한다.

2 + 3 * 4 ** 2 는 거듭제곱, 곱셈, 덧셈 순서로 계산되어 50 이 된다.

그림처럼 2 + 3 * 4 ** 2 는 먼저 4 ** 2 가 16 이 되고, 3 * 16 이 48 이 되고, 마지막에 2 + 48 이 50 이 된다. 눈에 보이는 왼쪽부터 계산하면 (2 + 3) * 4 = 20 이 되어 틀린다.

산술 연산자의 계산 순서(위쪽이 먼저)
연산자뜻같은 줄에 여럿일 때예
**거듭제곱오른쪽부터2 ** 3 ** 2 는 512
* /곱셈, 나눗셈왼쪽부터a / b * c 는 (a / b) * c
단항 + -부호곱셈보다 나중-2 ** 2 는 -4
이항 + -덧셈, 뺄셈왼쪽부터a - b + c 는 (a - b) + c

표에서 눈여겨볼 곳이 두 군데 있다. 첫째, ** 는 오른쪽부터 묶는다. 2 ** 3 ** 2 는 2 ** (3 ** 2) 이므로 2 의 9제곱인 512 다. 둘째, 맨 앞의 마이너스 부호는 거듭제곱보다 나중에 적용된다. 그래서 -2 ** 2 는 (-2) 의 제곱이 아니라 2 의 제곱에 마이너스를 붙인 -4 다. 변수로 쓰면 -base ** 2 도 같다. 제곱하려는 것이 음수라면 (-base) ** 2 처럼 괄호를 쳐야 한다.

우선순위를 외워서 쓰는 것보다 헷갈릴 만한 곳에 괄호를 치는 편이 낫다. 괄호는 계산 비용이 없고, 읽는 사람에게 의도를 알려 준다. 세 시각의 평균을 구할 때 t1 + t2 + t3 / 3.0_dp 라고 쓰면 나눗셈이 먼저 일어나 t3 만 3 으로 나뉜다. 평균은 (t1 + t2 + t3) / 3.0_dp 다.

거듭제곱에는 한 가지를 더 알아 둔다. 지수가 정수일 때 x ** 2 는 곱셈 한 번으로 계산되고 x 가 음수여도 된다. 지수가 실수인 x ** 2.0_dp 는 일반적인 거듭제곱 계산을 거치며, x 가 음수이면 결과가 정의되지 않는다. 제곱이나 세제곱에는 정수 지수를 쓴다.

정수 나눗셈과 형 변환

정수 나눗셈은 몫만 남긴다

정수를 정수로 나누면 결과도 정수다. 소수점 아래는 반올림되지 않고 버려진다. 135 분을 시간으로 바꾸려고 135 / 60 을 계산하면 2.25 가 아니라 2 가 나온다. 남는 15 분은 나머지 연산으로 얻는다. 내장 함수 mod(135, 60) 이 15 를 돌려준다. 몫과 나머지를 함께 쓰면 "2 시간 15 분"이 된다.

나머지 연산은 시각을 순환시킬 때도 쓴다. 22 시에서 5 시간 뒤는 27 시가 아니라 3 시다. mod(22 + 5, 24) 가 3 을 돌려준다.

음수가 끼면 두 가지 규칙을 구분해야 한다. 정수 나눗셈은 0 방향으로 잘라 버리므로 -7 을 3 으로 나누면 -2 다. mod(-7, 3) 은 "나눗셈 결과를 0 방향으로 자른 몫"을 기준으로 나머지를 정하기 때문에 -1 이고, 부호는 첫 번째 인자를 따른다. modulo(-7, 3) 은 "내림한 몫"을 기준으로 하므로 2 이고, 부호는 두 번째 인자를 따른다. 양수만 다루면 둘은 같다. 음수가 들어올 수 있으면 어느 쪽 의미가 필요한지 정하고 골라야 한다.

17 / 24 를 먼저 계산하면 0 이 되고, 100 을 먼저 곱하면 70 이 된다.

그림은 강수 시간의 비율을 구하는 두 가지 식을 비교한다. wet_hours / total_hours * 100 은 왼쪽부터 계산하므로 17 / 24 가 먼저 0 이 되고 100 을 곱해도 0 이다. 100 * wet_hours / total_hours 는 1700 / 24 가 되어 70 을 얻는다. 정수만으로 백분율을 구해야 한다면 곱셈을 앞에 두는 방법이 있다. 그래도 70.83 의 소수점 아래는 이미 버려졌다. 소수점 아래가 필요하면 아래의 형 변환을 써야 한다.

정수와 실수가 섞이면

연산자 하나의 양쪽 피연산자 종류가 다르면, 정수 쪽이 실수로 바뀐 뒤 계산한다. 100.0_dp * wet_hours 에서 17 은 17.0 의 real(dp) 로 바뀌어 1700.0 이 되고, 이어지는 / total_hours 에서도 24 가 실수로 바뀐다. 결과는 70.8333 이다.

이 변환은 식 전체가 아니라 연산자 하나마다 따로 일어난다는 점이 중요하다. 1 / 2 * 3.0_dp 에서는 1 / 2 가 먼저 정수 나눗셈으로 0 이 되고, 그 0 이 3.0 과 곱해져 0.0 이 된다. 실수가 식 어딘가에 있다고 해서 앞쪽의 정수 나눗셈까지 실수 나눗셈이 되지는 않는다.

변환을 직접 지정할 수도 있다. real(wet_hours, dp) 는 정수를 지정한 kind 의 실수로 바꾼다. 둘째 인자 dp 를 빼먹으면 기본 실수(단정밀도)가 되므로 앞 장에서 정한 kind 를 함께 쓴다. 이렇게 하면 real(wet_hours, dp) / total_hours 는 17.0 / 24 가 되어 0.7083 이다.

real, int, nint

실수를 정수로 바꾸는 함수가 둘 있다. int 는 소수점 아래를 버리고 0 방향으로 자른다. nint 는 가장 가까운 정수로 반올림하며, 정확히 .5 인 값은 0 에서 먼 쪽으로 올린다.

실수를 정수로 바꾸는 두 함수의 결과 비교
값intnint
2.523
-2.5-2-3
2.422
2.923

강수량 12.9 mm 를 "정수 mm 로 기록"할 때 int 는 12, nint 는 13 을 준다. 어느 쪽이 맞는지는 기록 규칙이 정한다. 규칙이 없다면 가장 가까운 값을 남기는 nint 가 보통 더 자연스럽다.

실수 값을 정수 변수에 그냥 대입하는 것도 문법상 허용되지만, 소수점 아래가 조용히 버려진다. 이 장의 컴파일 옵션 -Wall 은 이런 대입에 "값이 바뀔 수 있는 변환"이라는 경고를 낸다. 실수를 정수로 바꿀 때는 항상 int 나 nint 로 의도를 적는다.

내장 함수와 실수 비교

자주 쓰는 내장 함수

내장 함수는 언어가 기본으로 제공하는 함수로, 따로 선언하거나 가져오지 않고 바로 쓴다. 전체 목록은 GNU Fortran 의 내장 프로시저 문서에 있다. 이 장에서는 네 가지를 쓴다.

  • sqrt(x): 제곱근. 인자는 0 이상의 실수여야 한다. 바람의 동서 성분 u 와 남북 성분 v 로 풍속을 구할 때 sqrt(u ** 2 + v ** 2) 라고 쓴다.
  • abs(x): 절댓값. 정수와 실수 모두에 쓰며 결과는 인자와 같은 종류다.
  • mod(a, p): 나머지. 정수와 실수 모두에 쓴다.
  • max(a, b, ...), min(a, b, ...): 인자 가운데 가장 큰 값, 가장 작은 값. 인자를 두 개 이상 줄 수 있다.

max 와 min 의 인자는 모두 같은 종류여야 한다. max(3, 2.5_dp) 처럼 정수와 실수를 섞으면 컴파일 오류가 난다. 필요하면 max(real(3, dp), 2.5_dp) 로 한쪽을 바꿔 준다. 내장 함수는 다른 함수의 인자 자리에도 들어갈 수 있으므로 tol * max(abs(a), abs(b)) 같은 식도 자연스럽게 쓴다.

실수는 ==로 비교하지 않는다

컴퓨터는 실수를 2 진수로 저장한다. 10 진수 0.1 은 2 진수로는 끝나지 않는 소수여서, 저장된 값은 0.1 에 아주 가까운 수일 뿐이다. 0.1 과 0.2 를 더하면 이 작은 오차가 합쳐져 0.3 으로 저장된 값과 마지막 자리가 한 칸 어긋난다. 그래서 0.1_dp + 0.2_dp == 0.3_dp 는 거짓이 된다. 두 값의 차이는 약 5.55E-17 로 눈에 보이지 않을 만큼 작다.

해결책은 "충분히 가까운가"를 묻는 것이다. 허용 오차 tol 을 정해 두고 두 값의 차이의 절댓값이 tol 이하인지 본다.

close_enough = abs(a - b) <= tol

이 방식은 값의 크기를 고려하지 않는다. 값이 1.0E8 쯤이면 double 로 표현 가능한 인접한 두 값의 간격 자체가 1.0E-8 정도라서, 계산이 한 번만 어긋나도 차이가 1.0E-9 를 넘을 수 있다. 이런 경우에는 값의 크기에 비례하는 상대 허용 오차를 쓴다.

close_enough = abs(a - b) <= tol * max(abs(a), abs(b))

허용 오차의 크기는 kind 에 맞춘다. 내장 함수 epsilon(1.0_dp) 는 1 과 그다음으로 큰 수의 차이로, double 에서 약 2.2E-16 이다. 계산을 여러 번 거친 값은 오차가 쌓이므로 이 값보다 훨씬 큰 1.0E-9 정도를 허용 오차로 삼는 경우가 많다. 이 장에서 비교 결과는 논리 변수(logical)에 담아 출력한다. "~이면 ~한다"라는 분기는 다음 장에서 조건문과 함께 배운다.

완성 코드

아래 프로그램은 앞의 모든 규칙을 한 번씩 실행한다. 파일 이름은 main.f90 이다.

main.f90

program expressions
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  integer, parameter :: dp = real64
  real(dp), parameter :: tol = 1.0e-9_dp

  integer :: base, p1, p2, p3, p4
  real(dp) :: t1, t2, t3, wrong_mean, right_mean
  integer :: minutes, hours, rest, hour, later
  integer :: m, k
  integer :: wet_hours, total_hours, pct_wrong, pct_int
  real(dp) :: pct_real, ratio
  real(dp) :: r_pos, r_neg
  real(dp) :: u, v, speed, t_max, t_min
  real(dp) :: a, b, diff
  logical :: exact, abs_ok, rel_ok

  ! 1. 우선순위와 거듭제곱
  base = 2
  p1 = 2 + 3 * 4 ** 2
  p2 = -base ** 2
  p3 = (-base) ** 2
  p4 = base ** 3 ** 2
  print '(A,I0)', '2 + 3 * 4 ** 2 = ', p1
  print '(A,I0)', '-base ** 2 = ', p2
  print '(A,I0)', '(-base) ** 2 = ', p3
  print '(A,I0)', 'base ** 3 ** 2 = ', p4

  ! 2. 세 시각의 평균 기온
  t1 = 14.2_dp
  t2 = 15.8_dp
  t3 = 17.1_dp
  wrong_mean = t1 + t2 + t3 / 3.0_dp
  right_mean = (t1 + t2 + t3) / 3.0_dp
  print '(A,F8.2)', 'wrong_mean =', wrong_mean
  print '(A,F8.2)', 'right_mean =', right_mean

  ! 3. 정수 나눗셈과 나머지
  minutes = 135
  hours = minutes / 60
  rest = mod(minutes, 60)
  print '(I0,A,I0,A,I0,A)', minutes, ' min = ', hours, ' h ', rest, ' min'

  hour = 22
  later = mod(hour + 5, 24)
  print '(A,I0)', 'mod(22 + 5, 24) = ', later

  m = -7
  k = 3
  print '(A,I0)', '-7 / 3 = ', m / k
  print '(A,I0)', 'mod(-7, 3) = ', mod(m, k)
  print '(A,I0)', 'modulo(-7, 3) = ', modulo(m, k)

  ! 4. 강수 시간 비율: 정수 계산과 실수 계산
  wet_hours = 17
  total_hours = 24
  pct_wrong = wet_hours / total_hours * 100
  pct_int = 100 * wet_hours / total_hours
  pct_real = 100.0_dp * wet_hours / total_hours
  ratio = real(wet_hours, dp) / total_hours
  print '(A,I0)', 'wet / total * 100 = ', pct_wrong
  print '(A,I0)', '100 * wet / total = ', pct_int
  print '(A,F8.2)', 'real version =', pct_real
  print '(A,F8.3)', 'ratio =', ratio

  ! 5. int 와 nint
  r_pos = 2.5_dp
  r_neg = -2.5_dp
  print '(A,I0,A,I0)', 'int(2.5) = ', int(r_pos), '  int(-2.5) = ', int(r_neg)
  print '(A,I0,A,I0)', 'nint(2.5) = ', nint(r_pos), '  nint(-2.5) = ', nint(r_neg)

  ! 6. 내장 함수
  u = 3.0_dp
  v = 4.0_dp
  speed = sqrt(u ** 2 + v ** 2)
  t_max = max(t1, t2, t3)
  t_min = min(t1, t2, t3)
  print '(A,F8.2)', 'speed =', speed
  print '(A,F8.2)', 'abs(r_neg) =', abs(r_neg)
  print '(A,F8.2)', 'max - min =', t_max - t_min
  print '(A,I0)', 'max(hours, rest) = ', max(hours, rest)

  ! 7. 실수 비교
  a = 0.1_dp + 0.2_dp
  b = 0.3_dp
  diff = abs(a - b)
  exact = (a == b)
  abs_ok = diff <= tol
  rel_ok = diff <= tol * max(abs(a), abs(b))
  print '(A,L1)', 'a == b : ', exact
  print '(A,ES12.4)', 'abs(a - b) =', diff
  print '(A,L1)', 'abs_ok : ', abs_ok
  print '(A,L1)', 'rel_ok : ', rel_ok
end program expressions

줄별 해설

선언부. use, intrinsic :: iso_fortran_env, only: real64 와 dp 정의는 앞 장에서 다룬 방식 그대로다. tol 은 parameter 로 선언한 상수로, 실행 중에 바꿀 수 없다. 선언한 변수는 모두 아래에서 쓰인다. -Wall 은 쓰이지 않는 변수에 경고를 내기 때문이다.

1 번 블록. p1 은 50 이다. p2 = -base ** 2 는 base ** 2 를 먼저 계산하므로 -4, p3 는 괄호 덕분에 4, p4 = base ** 3 ** 2 는 base ** 9 이므로 512 다. 정수 출력은 I0 로, 자릿수에 맞는 최소 폭을 쓴다. 레이블 문자열이 "= " 로 끝나므로 숫자가 바로 붙는다.

2 번 블록. 같은 세 값으로 괄호 없는 식과 있는 식을 계산한다. 14.2 + 15.8 은 30.0, 여기에 17.1 / 3.0 = 5.7 을 더한 값이 35.7 이다. 올바른 식은 47.1 / 3.0 = 15.7 이다. 실수 출력은 F8.2 로, 전체 8칸에 소수점 아래 2 자리다. 숫자가 오른쪽 정렬되어 앞에 공백이 붙기 때문에 레이블은 "=" 로 끝낸다.

3 번 블록. minutes / 60 이 2, mod(minutes, 60) 이 15 이다. 출력 서식 '(I0,A,I0,A,I0,A)' 는 정수, 문자열, 정수, 문자열, 정수, 문자열을 번갈아 찍는다. 이어서 시각 순환은 mod(27, 24) 이므로 3 이다. 음수 쌍은 m / k 가 -2, mod 가 -1, modulo 가 2 다. m 과 k 를 변수로 둔 것은 의도가 있다. 상수끼리의 나눗셈은 컴파일러가 미리 계산하면서 정수 나눗셈이 잘렸다는 경고를 낼 수 있다.

4 번 블록. pct_wrong 은 17 / 24 가 0 이므로 0, pct_int 는 1700 / 24 의 몫 70 이다. pct_real 은 100.0_dp * wet_hours 가 1700.0 이고 24 로 나눈 70.83 이다. ratio 는 real(wet_hours, dp) 로 먼저 실수로 바꾼 뒤 나누어 0.708 이다. 출력 서식 F8.3 은 소수점 아래 3 자리다.

5 번 블록. int(r_pos) 는 2, int(r_neg) 는 -2, nint(r_pos) 는 3, nint(r_neg) 는 -3 이다. 한 줄에 서식 항목 네 개(A,I0,A,I0)와 출력 항목 네 개가 대응한다.

6 번 블록. sqrt(9 + 16) 이 5.0, abs(r_neg) 가 2.5 다. 17.1 에서 14.2 를 뺀 값은 약 2.9 이며 두 자리로 출력된다. max(hours, rest) 는 2 와 15 중 큰 15 다. 정수 둘이므로 max 의 인자 종류가 같다.

7 번 블록. a 는 0.1 과 0.2 를 더한 값, b 는 0.3 이다. exact 는 a == b 의 결과로 거짓이다. diff 는 5.5511E-17 이고 tol 은 1.0E-9 이므로 절대 허용 오차 비교 abs_ok 와 상대 허용 오차 비교 rel_ok 가 모두 참이다. 논리값은 L1 서식으로 한 글자 T 또는 F 를 출력한다. 차이는 아주 작은 값이라 지수 표기용 ES12.4 를 쓴다. 전체 12칸에 유효숫자 5 자리(소수점 앞 1 자리, 뒤 4 자리)다.

실행 결과

$ gfortran -std=f2018 -Wall -o main main.f90
$ ./main
2 + 3 * 4 ** 2 = 50
-base ** 2 = -4
(-base) ** 2 = 4
base ** 3 ** 2 = 512
wrong_mean =   35.70
right_mean =   15.70
135 min = 2 h 15 min
mod(22 + 5, 24) = 3
-7 / 3 = -2
mod(-7, 3) = -1
modulo(-7, 3) = 2
wet / total * 100 = 0
100 * wet / total = 70
real version =   70.83
ratio =   0.708
int(2.5) = 2  int(-2.5) = -2
nint(2.5) = 3  nint(-2.5) = -3
speed =    5.00
abs(r_neg) =    2.50
max - min =    2.90
max(hours, rest) = 15
a == b : F
abs(a - b) =  5.5511E-17
abs_ok : T
rel_ok : T

컴파일 단계에서는 아무 메시지도 나오지 않는다. 출력의 첫 줄부터 차례로 위 설명과 맞춰 보면 된다. 특히 wet / total * 100 이 0 이고 100 * wet / total 이 70 인 두 줄이 정수 나눗셈 규칙을 그대로 보여 준다.

실무에서 자주 틀리는 것

정수끼리 나눠 평균을 구한다

틀린 코드는 합계와 개수가 둘 다 정수라서 소수점 아래가 사라진다.

integer :: total_mm, n_days
real(dp) :: mean_mm
total_mm = 31
n_days = 2
mean_mm = total_mm / n_days     ! 15.0 이 된다. 15.5 가 아니다.

대입 대상이 실수여도 나눗셈은 우변에서 정수로 먼저 끝난다. 나누기 전에 한쪽을 실수로 바꾼다.

mean_mm = real(total_mm, dp) / n_days     ! 15.5

실수를 ==로 비교한다

틀린 코드는 계산으로 얻은 값이 목표 값과 같은지 정확히 묻는다.

logical :: reached
reached = (0.1_dp + 0.2_dp == 0.3_dp)     ! 거짓

허용 오차를 두고 차이의 크기를 본다. 값이 클 수 있으면 상대 허용 오차로 쓴다.

reached = abs((0.1_dp + 0.2_dp) - 0.3_dp) <= tol

괄호를 빼먹어 식이 의도와 다르게 묶인다

틀린 코드는 평균을 구하는 분자에 괄호가 없고, 음수의 제곱도 부호가 먼저 적용되는 것으로 착각한다.

mean = t1 + t2 + t3 / 3.0_dp      ! t3 만 3 으로 나뉜다
sq   = -base ** 2                  ! (-base) 의 제곱이 아니라 -(base ** 2)

의도하는 묶음을 괄호로 적는다.

mean = (t1 + t2 + t3) / 3.0_dp
sq   = (-base) ** 2

kind 를 붙이지 않은 실수 상수를 섞는다

상수에 _dp 를 붙이지 않으면 단정밀도 실수가 된다. 변수가 double 이어도 상수에서 이미 자릿수를 잃는다.

real(dp) :: third
third = 1.0 / 3.0          ! 0.33333334... 앞 7 자리쯤만 맞는다

상수마다 kind 를 붙인다.

third = 1.0_dp / 3.0_dp    ! 0.3333333333333333

한눈에 보기

정수와 실수 계산에서 쓰는 도구 정리
하고 싶은 일쓰는 것결과 예
정수 몫135 / 602
정수 나머지(첫 인자 부호)mod(-7, 3)-1
정수 나머지(둘째 인자 부호)modulo(-7, 3)2
정수를 실수로real(n, dp)17 을 17.0 으로
실수를 잘라 정수로int(-2.5_dp)-2
실수를 반올림해 정수로nint(2.5_dp)3
내장 함수와 비교 방법 정리
이름역할주의
sqrt(x)제곱근x 는 0 이상
abs(x)절댓값정수·실수 모두 가능
max(a, b, ...)가장 큰 값인자의 종류가 같아야 함
abs(a - b) <= tol절대 허용 오차 비교값이 크면 상대 오차 사용
abs(a - b) <= tol * max(abs(a), abs(b))상대 허용 오차 비교둘 다 0 근처면 절대 오차 병용

연습 문제

  1. a = 7, b = 2 인 정수 변수에서 a / b, -a / b, mod(-a, b), modulo(-a, b) 의 값을 각각 구하라.
  2. 하루 최고 기온 22 도와 최저 기온 9 도가 정수 변수 hi, lo 에 들어 있다. mean = (hi + lo) / 2 로 real(dp) 변수 mean 에 평균을 넣으면 값이 얼마인가. 15.5 가 나오도록 식을 고쳐라.
  3. 다음 네 값을 구하라: nint(12.5_dp), nint(12.4_dp), int(12.9_dp), nint(-0.5_dp).
  4. 실수 x, y 가 상대 오차 1.0E-6 이내로 같은지를 담는 논리식을 쓰고, 두 값이 모두 0 이면 어떻게 되는지 설명하라.

정답과 해설

  1. 3, -3, -1, 1 이다. 정수 나눗셈은 0 방향으로 자르므로 -7 / 2 = -3.5 에서 -3 이 된다. mod(-7, 2) 는 나머지가 첫 인자의 부호를 따라 -1 이고, modulo(-7, 2) 는 -7 = 2 * (-4) + 1 이므로 1 이다.
  2. (22 + 9) / 2 가 정수 나눗셈으로 31 / 2 = 15 가 되고, 그 15 가 15.0 으로 바뀌어 저장된다. 고친 식은 mean = (hi + lo) / 2.0_dp 이다. 분자가 정수 31 이어도 나누는 쪽이 실수이므로 31 이 31.0 으로 바뀌어 15.5 가 된다. real(hi + lo, dp) / 2 도 같은 결과다.
  3. 13, 12, 12, -1 이다. nint 는 .5 를 0 에서 먼 쪽으로 올리므로 12.5 는 13, -0.5 는 -1 이다. 12.4 는 가장 가까운 12 이고, int(12.9_dp) 는 버림이라 12 다.
  4. abs(x - y) <= 1.0e-6_dp * max(abs(x), abs(y)) 이다. x 와 y 가 모두 0 이면 오른쪽도 0 이 되어, 차이가 0 인 경우에만 참이 된다. 둘이 같은 0 이므로 이때는 참이다. 그러나 둘 다 0 에 매우 가깝고 서로 다르면 오른쪽이 극도로 작아져서 거의 항상 거짓이 된다. 이런 값을 비교한다면 abs(x - y) <= 1.0e-12_dp 처럼 절대 허용 오차를 함께 쓰는 쪽이 안전하다.
오탈자·오류 제보 비공개로 접수되어 원고 수정에 반영됩니다

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

READER FEEDBACK

질문·의견

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

댓글 0

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

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