Problem)
소수점이 0 인 실수들의 연산 결과가 True 이나, 소수점이 0 이 아닌(ex, 0.1234) 실수들의 경우 비록 연산은 참이어도 결과가 False로 출력된다. 아래 코드를 확인해보자.
print(2.0 - 1.0 == 1.0)
'''
True
'''
print(0.043 - 0.001 == 0.042)
'''
False
'''
A. 개념 정리 (간단히)
컴퓨터(Python 등)는 연산 과정을 2진수로 수행(계산)한다.
10진수를 정확히 2진수로 표현할 수 없는 경우가 대부분이다.
즉, 소수점을 포함한 실수(ex, 0.043, 0.001, 0.042)는 2진수로 정확하게 표현할 수 없다.
ex)
10을 2진수로 바꾸면, 10 (10진수) = 1010 (2진수) 가 되는데,
0.1를 2진수로 바꾸면, 10진수 0.1 = 1/10 = 1/(2×5) 로, 분모 10에는 2로 나누어 떨어지지 않는 5가 포함되어 있다.
[key] 2진법은 분모가 2의 거듭제곱으로만 표현할 수 있다.
위에서 1/5은 무한히 반복되는 2진소수(binary fraction) 이다.
즉 1/5 = 0.0011 0011 0011 0011...(2진수), 0.0011 다음의 0011이 무한히 반복된다.
다시 1/5을 10진수로 환산하면,
1/5 = 1/8 + 1/16 + 1/128 + 1/256 + ... = 0.2 에 수렴한다.
B. 코드 예제 및 출력 결과
아래와 같이,
소수점이 0인 실수 연산인 경우에는 결과 값이 기대 값과 일치하여 True가 출력되지만,
from decimal import Decimal, getcontext
getcontext().prec = 1 # 0보다 큰 원하는 자리수 설정
x = 2.0
y = 1.0
r = x - y
z = 1.0
print(f'x = {x} -> {Decimal(x):.30f}')
print(f'y = {y} -> {Decimal(y):.30f}')
print(f'r = x - y -> {Decimal(r):.30f}')
print(f'z = {z} -> {Decimal(z):.30f}')
'''
x = 2.0 -> 2.000000000000000000000000000000
y = 1.0 -> 1.000000000000000000000000000000
r = x - y -> 1.000000000000000000000000000000
z = 1.0 -> 1.000000000000000000000000000000
'''
print(x - y == z) # 2.0 - 1.0 = 1.0 -> correct!
'''
True
'''
print(r == z) # 1.0 = 1.0 -> correct!
'''
True
'''
위와 반하여,
소수점 이하 값이 있는 실수 연산인 경우에는 결과 값이 기대 값과 불일치하여 False가 출력된다. 이는 각 값들의 입력 값과 실제 출력 값들이 미세하게 차이가 있기 때문이다!
(2진수로 계산된 소수점의 10진수 값은 무한수렴하는 값으로 결국 근사 값으로 표현될 수 밖에 없다.)
a = 0.043
b = 0.001
r = a - b
c = 0.042
print(f'a = {a} -> {Decimal(a):.30f}')
print(f'b = {b} -> {Decimal(b):.30f}')
print(f'r = a - b -> {Decimal(r):.30f}')
print(f'c = {c} -> {Decimal(c):.30f}')
'''
a = 0.043 -> 0.042999999999999996558308623662
b = 0.001 -> 0.001000000000000000020816681712
r = a - b -> 0.041999999999999995670130203962
c = 0.042 -> 0.042000000000000002609024107869
'''
print(a - b == c) # 0.04299... - 0.00100... = 0.04200... -> incorrect!
print(r == c) # 0.04199... = 0.04200... -> incorrect!
'''
False
False
'''
여기에서,
Python(Colab)의 허용 오차 값이 어느 정도 미세하게 차이가 있을 때 True가 아닌 False가 되는지를 테스트 해보면,
실제 차이 값(r = a - b)은 약 6.939e-18 이고 (결과 : False),
허용 오차 값은 1e-17 이상 일 때 True 임을, 1e-18 이하 이면 False 임을 확인할 수 있다.
a = 0.043
b = 0.001
r = a - b
c = 0.042
print(f"a = 0.043 = {a:.30f}")
print(f"b = 0.001 = {b:.30f}")
print(f"r = a - b = {r:.30f}")
print(f"c = 0.042 -> {c:.30f}")
print(f"실제 차이 = {abs(r - c):.30e}")
# 1e-N에서 N = 1,2,...29,30까지, 즉 1e-1 ~ 1e-30까지 반복.
n = 30
for i in range(1, n+1):
differr = 10 ** (-i) # 10^-i = 1e-i
result = abs(r - c) < differr
print(f"허용 오차 값 : 1e-{i} → {result}")
'''
a = 0.043 = 0.042999999999999996558308623662
b = 0.001 = 0.001000000000000000020816681712
r = a - b = 0.041999999999999995670130203962
c = 0.042 -> 0.042000000000000002609024107869
실제 차이 = 6.938893903907228377647697925568e-18 <- ~ 6.939e-18으로 매우 작은 값!
허용 오차 값 : 1e-1 → True
허용 오차 값 : 1e-2 → True
허용 오차 값 : 1e-3 → True
허용 오차 값 : 1e-4 → True
허용 오차 값 : 1e-5 → True
허용 오차 값 : 1e-6 → True
허용 오차 값 : 1e-7 → True
허용 오차 값 : 1e-8 → True
허용 오차 값 : 1e-9 → True
허용 오차 값 : 1e-10 → True
허용 오차 값 : 1e-11 → True
허용 오차 값 : 1e-12 → True
허용 오차 값 : 1e-13 → True
허용 오차 값 : 1e-14 → True
허용 오차 값 : 1e-15 → True
허용 오차 값 : 1e-16 → True
허용 오차 값 : 1e-17 → True
허용 오차 값 : 1e-18 → False <= Oh! my sence!
허용 오차 값 : 1e-19 → False
허용 오차 값 : 1e-20 → False
허용 오차 값 : 1e-21 → False
허용 오차 값 : 1e-22 → False
허용 오차 값 : 1e-23 → False
허용 오차 값 : 1e-24 → False
허용 오차 값 : 1e-25 → False
허용 오차 값 : 1e-26 → False
허용 오차 값 : 1e-27 → False
허용 오차 값 : 1e-28 → False
허용 오차 값 : 1e-29 → False
허용 오차 값 : 1e-30 → False
'''
C. 핵심 요약 및 대응 방안
Python에서 0.1과 같은 소수점 이하는 무한소수를 근사해서 2진 부동소수점*으로 저장하고, 그 허용 오차는 약 1.04e-17 정도로 매우 작다고 한다.
*IEEE 754 표준 (double precision, 64-bit) : 컴퓨터가 실수를 2진수로 표현하고 연산하는 방법을 정의한 국제 표준. [참조. ChatGPT5]
출력 결과를 간단히 표로 비교해보면 아래와 같다.
| 테스트한 허용 오차 값 |
알려진 국제 표준 허용 오차 값* |
두 값의 실제 차이 (r - c) |
출력 결과 |
| 1e-16 이상 | True | ||
| 1e-17 | ~ 1.04e-17 | True , OK! :) | |
| ~ 6.94e-18 | False | ||
| 1e-18 이하 | False |
출력 결과 False를 True로 출력되게 하려면, 허용 오차 값이 그 표준 허용 오차 값(~1e-17) 보다 크도록 강제하면 될 것 이다.
Python 3.5 이상 버전에서 쉽게 다룰 수 있는 가장 권장되는 함수, math.isclose(), 가 유용하다.
이 함수는 상대 오차 허용치가 1e-9 보다 작으면, 소수점 연산도 근사하여 True로 출력한다.
import math
a = 0.043
b = 0.001
r = a - b
c = 0.042
print(math.isclose(r, c))
print(f'{abs(r-c)/c}') # ~ 1.652e-16 < 1e-9 (기본 허용 오차)
'''
True
1.6521175961683875e-16
'''
함수 math.isclose() 의 원리는 다음과 같다.
파이썬 공식 문서, Python 3.12, 기준에 따르면,
math.isclose(a, b, rel_tol = 1e-9, abs_tol = 0.0)
두 값의 차이 : |a − b| ≤ max(rel_tol × max(|a|, |b|), abs_tol)
> rel_tol: 상대 오차 허용치 (기본 1e-9)
> abs_tol: 절대 오차 허용치 (값이 작을 때 보정용)
즉 두 값의 차이가 |a − b| ≤ rel_tol × max(|a|, |b|) 이면(1e-9 보다 작으면),
True로 출력된다.
추가로,
b를 미세하게 바꾸어서 오차에 대한 결과를 비교하여 확인해보자.
b = 0.001 000 000 1 일 때는 오차가 허용 오차보다 크므로 (2.38e-9 > 1e-9) False가 되고,
b = 0.001 000 000 01 일 때는 오차가 허용 오차보다 작으므로 (2.38e-10 < 1e-9) True가 된다.
a = 0.043
b = 0.0010000001
r = a - b
c = 0.042
print(math.isclose(r, c))
print(f'{abs(r-c)/c}') # abs() : 절대값
'''
False
2.380952577953264e-09
'''
a = 0.043
b = 0.00100000001
r = a - b
c = 0.042
print(math.isclose(r, c))
print(f'{abs(r-c)/c}')
'''
True
2.3809542300708603e-10
'''
보통 오차가 1e-9 = 0.000 000 001 이면(10억분의 1) 대부분의 소수점 연산 경우에 충분할 것이다. 다만, 소수점 자리의 값에 대한 일치 여부(True or False)는 반드시 소수점의 근사 값의 적용 여부에 따라 달라짐을 명심하자.
[주의] 내용에 오류가 있을 수 있습니다.

'[Code] Study & Practice' 카테고리의 다른 글
| [Python] 자료 구조4 - 이진 트리(Binary Tree)의 개념, 구조, 규칙 (0) | 2025.11.01 |
|---|---|
| [Python] 자료 구조3 - Hash table의 개념과 index 충돌 해결 방식 비교 (0) | 2025.10.30 |
| [Python] 자료 구조2 - Single & Double Linked List(연결 리스트)의 개념과 차이점 (1) | 2025.10.28 |
| [Python] 자료 구조1 - 스택(Stack)과 큐(Queue)의 개념과 차이점 (0) | 2025.10.27 |
| [Python] 메모리(RAM)와 변수(Variable)의 개념. (0) | 2025.10.22 |