부동 소수점 - 4. 부동 소수점 연산의 한계

2025. 2. 8. 18:02소프트웨어/기초

부동 소수점 연산의 한계

부동 소수점 연산은 우리가 예상하는 정확한 값과 다른 결과를 반환할 수 있습니다.
예를 들어, 다음과 같은 연산을 수행할 때:

print(0.1 + 0.2)  # 예상: 0.3, 실제 출력: 0.30000000000000004

우리는 0.1 + 0.2 = 0.3이 나와야 한다고 생각하지만, 실제 출력값은 0.30000000000000004입니다.
이러한 오차는 부동 소수점 방식이 실수를 근사값으로 저장하기 때문에 발생합니다.


1. 왜 부동 소수점 연산이 정확하지 않을까?

(1) 일부 10진 소수는 2진수로 정확히 표현할 수 없음

컴퓨터는 실수를 **2진법(0과 1)**으로 저장하는데, 일부 10진수는 2진법으로 정확하게 변환할 수 없습니다.

예를 들어, 10진수 0.1을 2진수로 변환하면 무한 반복되는 소수가 됩니다.

  • 10진법에서는 0.1을 정확하게 저장할 수 있지만,
  • 2진법에서는 끝없이 반복되므로, 컴퓨터는 일정한 비트 수에서 근사값으로 저장하게 됩니다.

이 때문에 0.1, 0.2 등의 숫자는 정확하게 표현되지 않고, 연산 과정에서 오차가 발생합니다.


(2) IEEE 754 부동 소수점 표준의 제한

부동 소수점 숫자는 IEEE 754 표준을 따르며, 실수를 다음과 같이 저장합니다.

  • 32비트(단정밀도)에서는 23비트의 가수(Mantissa)만 사용할 수 있습니다.
  • 64비트(배정밀도)에서는 52비트의 가수(Mantissa)만 사용할 수 있습니다.
  • 따라서, 일부 소수는 주어진 비트 안에서 정확히 저장할 수 없으며, 근사값으로 저장됨

(3) 부동 소수점 연산 과정에서 발생하는 누적 오차

부동 소수점 숫자는 단순한 덧셈, 뺄셈뿐만 아니라 곱셈, 나눗셈에서도 오차가 누적될 수 있습니다.

예제 1: 작은 값이 반복적으로 더해질 때 누적되는 오차

result = 0.0
for _ in range(100):
    result += 0.1

print(result)  # 예상: 10.0, 실제 출력: 9.99999999999998

🔹 원인: 0.1이 정확하게 표현되지 않기 때문에, 반복적으로 더하면 오차가 누적됨.


예제 2: 복잡한 연산에서 오차가 증폭되는 경우

import math

# π 값을 이용한 원둘레 계산
radius = 1.1
circumference = 2 * math.pi * radius

print(circumference)  # 예상: 6.9115..., 실제 출력: 6.911503837897545

🔹 원인: math.pi 자체가 IEEE 754 형식으로 저장된 근사값이기 때문에, 연산을 거듭할수록 오차가 누적됨.


2. IEEE 754의 특수한 값들

IEEE 754 표준에서는 특수한 값들을 정의하여, 연산 중 예외적인 상황을 처리할 수 있도록 설계되어 있습니다.

특수 값 설명  예제 연산 Python 출력값
Infinity (무한대, ∞) 0으로 나누거나 매우 큰 숫자를 표현할 때 발생 1.0 / 0.0 inf
-Infinity (-∞) 음의 무한대 -1.0 / 0.0 -inf
NaN (Not a Number) 연산이 정의되지 않은 경우 0.0 / 0.0 nan

예제 3: IEEE 754 특수 값 확인

import math

# 무한대 연산
print(1.0 / 0.0)  # inf
print(-1.0 / 0.0)  # -inf

# NaN 연산
print(math.sqrt(-1))  # nan
print(0.0 / 0.0)  # nan

🔹 주의: NaN은 어떤 숫자와 비교해도 False를 반환합니다.

print(float('nan') == float('nan'))  # False

NaN은 자기 자신과도 같지 않음!


3. 부동 소수점 연산 오차를 해결하는 방법

(1) 반올림(round) 사용

작은 오차를 없애기 위해 round() 함수를 사용할 수 있습니다.

print(round(0.1 + 0.2, 10))  # 출력: 0.3

하지만, 이 방법은 단순히 출력값을 조정하는 것일 뿐, 근본적인 문제를 해결하지는 않습니다.


(2) decimal 모듈 사용 (정확한 소수 연산)

decimal 모듈을 사용하면 10진법 그대로 연산할 수 있어, 부동 소수점 오차를 방지할 수 있습니다.

from decimal import Decimal

a = Decimal('0.1')
b = Decimal('0.2')

print(a + b)  # 출력: 0.3 (정확한 값)
  • decimal.Decimal을 사용하면 0.1과 0.2가 정확하게 저장되므로 연산 결과도 정확합니다.
  • 금융 계산 등 정확한 소수점 연산이 필요한 경우에 사용됩니다.

(3) fractions 모듈 사용 (분수 연산)

부동 소수점 대신 분수(Fraction)로 연산하면 정확도를 유지할 수 있습니다.

from fractions import Fraction

a = Fraction(1, 10)  # 1/10 (0.1)
b = Fraction(2, 10)  # 2/10 (0.2)

print(a + b)  # 출력: 3/10
  • 분수 형태로 저장하여 부동 소수점 오차 없이 정확한 계산이 가능합니다.
  • 하지만 연산 속도가 느릴 수 있으므로, 특정 경우에만 사용하는 것이 좋습니다.

4. 정리

부동 소수점 연산은 정확하지 않으며, 일부 숫자는 2진법으로 정확히 표현할 수 없다.
0.1 같은 숫자는 2진수로 변환 시 무한 반복되므로, 컴퓨터는 근사값으로 저장한다.
부동 소수점 연산 과정에서 오차가 누적될 수 있으며, 금융 계산 등에는 적절하지 않다.
IEEE 754에는 특수한 값(NaN, Infinity 등)이 있으며, 예외적인 연산 처리를 가능하게 한다.
오차를 해결하기 위해 round(), decimal 또는 fractions 모듈을 활용할 수 있다.