수치해석
재료공학도를 위한 수치해석
1. 유한정밀도
- 컴퓨터는 정보를 0과 1, 즉 이진수(binary number)로 저장한다.
- 실수(real number)는 무한히 많은 자릿수를 가질 수 있지만 컴퓨터의 메모리는 한정되어 있으므로 정해진 비트 수까지만 저장할 수 있다(예: 32비트, 64비트).
- 따라서 많은 실수는 정확한 값이 아니라 가까운 값으로 저장된다. 이를 컴퓨터가 유한정밀도(finite precision)를 갖는다고 표현한다.
2. 부동소수점 (floating point)
-
부동소수점은 수를 부호(sign), 지수(exponent), 가수(fraction)로 나누어 이진수로 표현하는 방식이다.
-
비교할 수 있는 다른 방식으로 고정소수점(fixed point)이 있다.
여기서 $s$는 부호 비트, $E$는 저장된 지수, $f$는 저장된 가수이다. 이 식은 정규화된 유한한 수(normalized finite number)에 적용된다. 0, 비정규수, 무한대(Infinity)와 NaN(Not a Number)은 별도의 규칙으로 표현한다.
2.1. 정수를 이진수로 나타내기
- 10진수 정수를 이진수로 바꾸는 방법은 2로 나누어 몫과 나머지를 구하는 것이다.
-
예를 들어, 10진수 13을 이진수로 바꾸어보자.
몫 나머지 --------------------- 13 / 2 = 6 ... 1 6 / 2 = 3 ... 0 3 / 2 = 1 ... 1 1 / 2 = 0 ... 1따라서 13의 이진수는
1101다르게 표현하자면 \(13=\textcolor{red}{1}\times2^3+\textcolor{red}{1}\times2^2+\textcolor{red}{0}\times2^1+\textcolor{red}{1}\times2^0\)
2.2. 10진수 5.75 저장하기
- 5 =
101: $5=\textcolor{red}{1}\times2^2+\textcolor{red}{0}\times2^1+\textcolor{red}{1}\times2^0$ - 0.75 =
0.11
0.75를 이진수로 만들기 위해서는 우선 2를 곱한다:
\[0.75\times 2 = 1.5\]정수 부분 1을 기록하고, 나머지 소수 부분 0.5에 다시 2를 곱한다.
정수 부분 1을 기록하면 나머지가 0이므로 계산을 마친다. 따라서 $5.75_{10}=101.11_2$이다. 이 값을 정규화한 뒤 부호, 지수, 가수로 저장한다.
즉 \(5.75=\textcolor{red}{1}\times2^2+\textcolor{red}{0}\times2^1+\textcolor{red}{1}\times2^0+\textcolor{red}{1}\times2^{-1}+\textcolor{red}{1}\times2^{-2}\)
2.2.1. 정규화(normalization)
IEEE 754의 정규화된 이진 부동소수점 수는
\[1.\text{XXXX..} \times 2^n\]형태로 바꾸어 저장한다. 따라서 이진수 101.11을 다음과 같이 변환한다.
과 같이 표현한다.
-
가수(fraction) = $\green{0111}0000…$ (남는 비트는 0으로 채운다.)
-
지수(exponent) = $\red{2}$
-
밑이 2인 이유는 이진수를 사용하기 때문이다.
2.2.2. 부호
5.75는 양수이므로 부호 비트는 0이다.
2.2.3. 지수
IEEE 754에 의하여 지수를 바이어스(bias) 방식으로 저장한다.
- 배정밀도(double precision)의 바이어스는 $\blue{1023}$이다.
- 실제 지수는 $\red{2}$이다.
- 저장되는 지수는 $\red{2}+\blue{1023}=1025$이다.
- 바이어스를 적용한 1025를 이진수로 표현해보자.
몫 나머지
---------------------
1025 / 2 = 512 ... 1
512 / 2 = 256 ... 0
256 / 2 = 128 ... 0
128 / 2 = 64 ... 0
64 / 2 = 32 ... 0
32 / 2 = 16 ... 0
16 / 2 = 8 ... 0
8 / 2 = 4 ... 0
4 / 2 = 2 ... 0
2 / 2 = 1 ... 0
1 / 2 = 0 ... 1
따라서 저장되는 지수는 10000000001(11비트)이다.
2.2.4. 가수부 (fraction)
정규화된 수 $1.0111\times2^2$에서 소수점 앞의 1은 항상 존재하므로 저장하지 않는다. 이를 숨겨진 비트(hidden bit)라고 한다. 따라서 0111 뒤를 0으로 채운 52비트가 가수에 저장된다.
-
5.75를 IEEE 754 방식으로 저장하면 다음과 같다. - 부호:
0(1비트) - 지수:
10000000001(11비트) - 가수:
0111000000000000000000000000000000000000000000000000(52비트)
따라서 전체 비트열은 다음과 같다.
0 10000000001 0111000000000000000000000000000000000000000000000000
2.3. 예제
2.3.1. 10진수 1.5 저장하기
- 1=
1 - 0.5 = ?
정수 부분 1이 나오고 나머지가 0이므로 $0.5_{10}=0.1_2$이다. 따라서
가 된다.
- 정규화는?
으로 해야하므로,
\[1.1\rightarrow1.1 \times 2^0\]가수와 지수를 구하면 다음과 같다.
- 가수: $10000…$
- 실제 지수: 0
- 부호 비트: 0
- 바이어스를 적용한 지수: $0+1023=1023$
1023을 이진수로 표현하면 다음과 같다.
1023/2=511 ... 1
511 /2=255 ... 1
255 /2=127 ... 1
127 /2=63 ... 1
63 /2=31 ... 1
31 /2=15 ... 1
15 /2=7 ... 1
7 /2=3 ... 1
3 /2=1 ... 1
1. /2=0. ... 1
계산 결과는 1111111111(10비트)이지만 지수 필드는 11비트이므로 앞에 0을 붙인 01111111111로 저장한다.
-
정규화된 수 $1.1\times2^0$에서 앞자리
1은 숨기고, 소수점 아래의1뒤에 0을 채운 52비트를 가수로 저장한다. - 부호:
0(1비트) - 지수:
01111111111(11비트) - 가수:
1000000000000000000000000000000000000000000000000000(52비트)
0 01111111111 1000000000000000000000000000000000000000000000000000
2.3.2. 단정밀도(single precision)에서 0.5 저장하기
IEEE 754 단정밀도(single precision)는 총 32비트를 사용한다.
- 부호: 1비트
- 지수: 8비트
- 가수: 23비트
- 바이어스: $\color{blue}{127}$
10진수 0.5를 이진수로 나타내고 정규화하면
\[0.5_{10}=0.1_2=1.0_2\times2^{-1}\]이다. 양수이므로 부호 비트는 0이다. 실제 지수는 $-1$이므로 저장되는 지수는
이다. 정규화된 가수에서 숨겨진 1 뒤에는 모두 0만 있으므로 전체 비트열은
0 01111110 00000000000000000000000
이다. $0.5=2^{-1}$이므로 단정밀도에서도 반올림 오차 없이 정확히 저장할 수 있다.
단정밀도는 약 7자리, 배정밀도는 약 15~16자리의 10진 유효숫자를 표현할 수 있다.
2.3.3. 0.1을 저장한다면
10진 소수의 소수 부분에 2를 반복해서 곱하고, 각 결과의 정수 부분을 위에서부터 차례로 읽으면 이진수의 소수점 아래 자릿수를 구할 수 있다.
| 계산 순서 | 2를 곱한 결과 | 정수 부분 | 다음 계산에 사용할 소수 부분 |
|---|---|---|---|
| 1 | $0.1\times2=0.2$ | 0 | 0.2 |
| 2 | $0.2\times2=0.4$ | 0 | 0.4 |
| 3 | $0.4\times2=0.8$ | 0 | 0.8 |
| 4 | $0.8\times2=1.6$ | 1 | 0.6 |
| 5 | $0.6\times2=1.2$ | 1 | 0.2 |
| 6 | $0.2\times2=0.4$ | 0 | 0.4 |
| 7 | $0.4\times2=0.8$ | 0 | 0.8 |
| 8 | $0.8\times2=1.6$ | 1 | 0.6 |
| 9 | $0.6\times2=1.2$ | 1 | 0.2 |
정수 부분을 위에서부터 순서대로 읽으면
\[0.1_{10}=0.00011001100110011\ldots_2\]가 된다. 5번째 계산이 끝난 뒤 소수 부분이 다시 0.2가 되어 2번째 계산과 같은 상태로 돌아간다. 따라서 0011이 계속 반복된다.
이를 부동소수점 형식에 맞게 정규화하면 다음과 같다.
\[0.0001100110011\ldots_2 =1.100110011\ldots_2\times2^{-4}\]하지만 가수에 사용할 수 있는 비트 수는 한정되어 있으므로 반복되는 모든 자릿수를 저장할 수 없다. 컴퓨터는 일정한 위치에서 이진수를 반올림하여 0.1에 가장 가까운 값을 저장한다. 따라서 Python에서 자릿수를 길게 출력하면 다음과 같이 보인다.
x = 0.1
print(f"{x:.20f}")
0.10000000000000000555
즉, 컴퓨터에 저장된 값은 수학적으로 정확한 $0.1$이 아니라 $0.1$에 매우 가까운 부동소수점 근삿값이다.
- \(0.1=\textcolor{red}{0}\times 2^{-1}+\textcolor{red}{0}\times 2^{-2}+\textcolor{red}{0}\times 2^{-3}+\textcolor{red}{1}\times 2^{-4}+\textcolor{red}{1}\times 2^{-5}+\cdots\)
2.3.4. 큰 수와 작은 수를 더할 때
\[10^{16}+1-10^{16}=?\]print(1e16+1-1e16)
- 연산 순서에 따라 결과가 달라질 수 있음
# 예제 1: 큰 수 + 작은 수
a = 1e16 # 매우 큰 수
b = 1.0 # 작은 수
c = -1e16 # 큰 음수
res1 = (a + b) + c
res2 = (a + c) + b
print("(a + b) + c =", res1)
print("(a + c) + b =", res2)
실행 결과는 다음과 같다.
(a + b) + c = 0.0
(a + c) + b = 1.0
첫 번째 계산에서는 $10^{16}$에 1을 더할 때 두 수의 크기 차이가 너무 커서 작은 수 1이 표현 정밀도의 한계로 사라진다. 두 번째 계산에서는 큰 양수와 큰 음수를 먼저 더한 뒤 1을 더하므로 정확한 결과를 얻는다. 따라서 컴퓨터의 부동소수점 덧셈에서는 연산 순서에 따라 결과가 달라질 수 있다.
2.3.5. 표현 범위와 정밀도 확인하기
NumPy의 finfo를 사용하면 부동소수점 자료형의 범위와 정밀도를 확인할 수 있다.
import numpy as np
for dtype in [np.float32, np.float64]:
info = np.finfo(dtype)
print(dtype)
print(" epsilon =", info.eps)
print(" smallest positive normal =", info.tiny)
print(" largest finite value =", info.max)
여기서 eps는 1보다 큰 수 중 1과 가장 가까운 표현 가능한 수와 1의 차이이다.
eps = np.finfo(np.float64).eps
print(1.0 + eps == 1.0)
print(1.0 + eps/2 == 1.0)
일반적인 배정밀도 환경에서는 첫 번째 비교가 False, 두 번째 비교가 True로 출력된다. 이는 수를 구별할 수 있는 간격이 유한하며, 그 간격보다 작은 변화는 저장 과정에서 사라질 수 있음을 보여준다.
3. 연습 문제
강의에서 배운 내용을 확인하는 기초 문제이다. 계산 문제는 풀이 과정도 함께 작성하라.
3.1. 기본 개념
-
다음 빈칸을 채워라.
- 컴퓨터는 모든 정보를 ( )과 ( )으로 저장한다.
- 실수를 저장할 때는 부호, ( ), 가수로 나누어 저장한다.
- 저장할 수 있는 비트 수가 정해져 있기 때문에 컴퓨터는 ( ) 정밀도를 갖는다.
-
다음 설명이 맞으면 O, 틀리면 X로 표시하라.
- 컴퓨터는 모든 실수를 정확하게 저장할 수 있다. ( )
- $0.5$는 이진수로 유한하게 표현할 수 있다. ( )
- $0.1$은 이진수로 표현하면 무한히 반복된다. ( )
- IEEE 754에서는 부호, 지수, 가수를 이용하여 부동소수점 수를 저장한다. ( )
-
64비트 배정밀도 부동소수점에서 각 부분이 사용하는 비트 수를 적어라.
구분 비트 수 부호 지수 가수 합계 64 -
양수와 음수의 부호 비트를 각각 적어라.
- 양수의 부호 비트:
- 음수의 부호 비트:
- 양수의 부호 비트:
3.2. 이진수와 정규화
-
다음 이진수를 10진수로 바꾸어라.
- $10_2$
- $101_2$
- $11.1_2$
소수점 아래 첫 번째 자리는 $2^{-1}=1/2$임을 이용하라.
-
다음 10진수를 이진수로 바꾸어라.
- $2$
- $3$
- $0.5$
- $2.5$
-
다음 이진수를 $1.\text{XXXX}\times2^n$ 형태로 정규화하고 지수 $n$을 구하라.
- $10.1_2$
- $101.0_2$
- $0.11_2$
-
10진수 $1.5$는 이진수로 $1.1_2$이다. 64비트 배정밀도로 저장할 때 다음 빈칸을 채워라.
- 정규화된 표현: $1.1_2\times2^{( )}$
- 부호 비트:
- 실제 지수: $ $
- 바이어스를 더한 지수: $ +1023= $
- 숨겨진 첫 번째
1을 제외한 가수의 첫 비트:
3.3. Python 실습
-
다음 코드를 실행하고 출력 결과를 기록하라.
print(0.5 + 0.25) print(0.1 + 0.2) print((0.5 + 0.25) == 0.75) print((0.1 + 0.2) == 0.3)두 비교 결과가 다른 이유를 유한정밀도와 관련지어 한 문장으로 설명하라.
-
다음 코드를 실행하여 연산 순서에 따른 결과를 비교하라.
a = 1e16 b = 1.0 c = -1e16 result1 = (a+b)+c result2 = (a+c)+b print(result1) print(result2)- 수학적으로 $a+b+c$의 값은 얼마인가?
-
result1과result2의 값을 각각 기록하라. - 두 결과가 달라지는 이유를 설명하라.
-
Python이 저장한 $0.1$을 소수점 아래 20자리까지 출력해보자.
x = 0.1 print(f"{x:.20f}")출력된 값이 정확히
0.10000000000000000000인지 확인하고 관찰한 내용을 적어라. -
NumPy를 이용하여 32비트와 64비트 부동소수점 수를 만들어 비교하라.
import numpy as np x32 = np.float32(1.0/3.0) x64 = np.float64(1.0/3.0) print(x32) print(x64)어느 자료형이 더 많은 자릿수를 표현하는지 답하라.