Skip to content

고정 소수점과 부동 소수점 (Fixed Point · Floating Point) ​

컴퓨터는 모든 것을 0과 1로 저장하기 때문에, 3.14 같은 실수를 저장하려면 "소수점을 어디에 찍을지"를 정하는 규칙이 필요합니다. 그 규칙이 고정 소수점과 부동 소수점 두 가지입니다.

한눈에 보기 ​

  • 고정 소수점: 소수점 자리를 미리 정해 놓는 방식입니다.
    • 비유: 칸이 정해진 가격표. "정수 5칸, 소수 2칸"으로 정해 두면 00123.45처럼만 쓸 수 있습니다. 단순하지만 아주 큰 수나 아주 작은 수는 칸이 모자랍니다.
  • 부동 소수점: 소수점 위치를 숫자와 함께 따로 적어 두는 방식입니다. "부동(浮動)"은 떠서 움직인다는 뜻입니다.
    • 비유: 과학 시간의 표기법 1.23 × 10의 5제곱. 유효 숫자(1.23)와 "소수점을 몇 칸 옮길지"(5)를 따로 적으니, 아주 큰 수와 아주 작은 수를 같은 칸 수로 표현할 수 있습니다.

고정 소수점 (Fixed Point) ​

소수점이 찍힐 위치를 미리 정해 놓고 소수를 표현하는 방식 (정수부 + 소수부)

예를 들어 -3.141592를 표현하려면 부호(-), 정수부(3), 소수부(0.141592) 세 가지 요소가 필요합니다.

32비트를 쓰는 예로, 부호 1비트, 정수부 15비트, 소수부 16비트로 나눠 쓴다면 다음과 같습니다.

부호 1비트, 정수부 15비트, 소수부 16비트로 나눈 32비트 고정 소수점 방식

그림 출처: TCP School, 실수형 타입

text
 1비트    15비트                16비트
┌──────┬────────────────────┬────────────────────┐
│ 부호 │      정수부         │      소수부         │
└──────┴────────────────────┴────────────────────┘
                            ▲
                   소수점 위치가 여기로 고정
  • 장점: 실수를 정수부와 소수부로 나눠 표현하므로 단순합니다. 사실상 정수 하나로 저장하고 계산하기 때문에 연산도 정수 연산처럼 빠르고 예측 가능합니다.
  • 단점: 표현할 수 있는 범위가 좁습니다. 위 예에서는 정수부가 15비트뿐이라 약 ±32767까지만 표현되고, 소수부도 16비트라 2의 16제곱분의 1(약 0.0000153)보다 촘촘하게는 표현할 수 없습니다.

정수부와 소수부를 몇 비트씩 쓸지는 정해진 규칙이 아니라 설계하는 사람이 정합니다. 15비트와 16비트는 흔히 쓰는 예시 중 하나일 뿐입니다.

코드로 보기 ​

소수 부분에 16비트를 쓰는 고정 소수점은 "실수에 2의 16제곱(65536)을 곱해서 정수로 저장"하는 것과 같습니다.

python
# 소수 부분에 16비트를 쓰는 고정 소수점 (1을 2**16 = 65536으로 나눈 칸 단위로 저장)
SCALE = 2 ** 16

def to_fixed(x):
    return round(x * SCALE)          # 정수 하나로 저장

def from_fixed(n):
    return n / SCALE

a = to_fixed(-3.141592)
print(a)                    # 실제로 저장되는 정수
print(from_fixed(a))        # 다시 실수로 바꾸면
print(from_fixed(to_fixed(1.5) + to_fixed(2.25)))   # 덧셈은 정수 덧셈 그대로
print(1 / SCALE)            # 표현할 수 있는 가장 작은 간격
text
-205887
-3.1415863037109375
3.75
1.52587890625e-05

-3.141592가 -3.1415863...으로 살짝 달라졌습니다. 소수부 칸이 16비트라 그보다 더 정밀하게는 저장할 수 없기 때문입니다. 고정 소수점도 오차가 없는 것은 아니고, 오차의 크기가 일정하다는 특징이 있습니다.

부동 소수점 (Floating Point) ​

실수를 가수부 + 지수부로 표현하는 방식

  • 가수(Mantissa): 실수의 실제 값(유효 숫자)을 표현합니다.
  • 지수(Exponent): 크기를 표현합니다. 가수의 어디쯤에 소수점이 있는지를 나타냅니다.

지수의 값에 따라 소수점이 움직이는 방식을 활용한 실수 표현 방법입니다. 즉, 소수점의 위치가 고정되어 있지 않습니다.

32비트 부동 소수점의 구조 (IEEE 754) ​

오늘날 거의 모든 컴퓨터는 IEEE 754라는 표준을 따릅니다. 32비트(단정밀도, float)는 이렇게 나눠 씁니다.

부호 1비트, 지수부 8비트, 가수부 23비트로 나눈 IEEE 754 32비트 부동 소수점 방식

그림 출처: TCP School, 실수형 타입

text
 1비트      8비트                      23비트
┌──────┬──────────────┬──────────────────────────────────┐
│ 부호 │    지수부     │              가수부               │
└──────┴──────────────┴──────────────────────────────────┘

 값 = (-1)^부호 × 1.가수 × 2^(지수 - 127)
  • 2진수로 1.xxxx × 2의 n제곱 꼴로 맞추면(정규화) 맨 앞자리는 항상 1이므로, 그 1은 저장하지 않고 소수점 아래 23비트만 가수부에 저장합니다.
  • 지수는 음수도 필요하므로, 실제 지수에 127을 더한 값을 저장합니다. 이 127을 바이어스(bias) 라고 합니다.
  • 64비트(배정밀도, double)는 부호 1비트, 지수 11비트, 가수 52비트를 씁니다. Python의 float는 64비트입니다.

코드로 보기 ​

Python의 struct 모듈로 실수가 32비트로 어떻게 저장되는지 직접 확인해 보겠습니다.

python
import struct

def float32_bits(x):
    # 실수를 32비트 부동 소수점으로 저장했을 때의 비트를 문자열로 돌려준다
    n = struct.unpack('>I', struct.pack('>f', x))[0]
    b = f'{n:032b}'
    return b[0], b[1:9], b[9:]

for x in [-3.141592, 0.1]:
    s, e, m = float32_bits(x)
    print(f'{x}: 부호={s} 지수={e} ({int(e, 2)} - 127 = {int(e, 2) - 127}) 가수={m}')

print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
print(struct.unpack('>f', struct.pack('>f', 0.1))[0])
text
-3.141592: 부호=1 지수=10000000 (128 - 127 = 1) 가수=10010010000111111011000
0.1: 부호=0 지수=01111011 (123 - 127 = -4) 가수=10011001100110011001101
0.30000000000000004
False
0.10000000149011612
  • -3.141592는 음수라 부호가 1이고, 1.57... × 2의 1제곱으로 저장되어 실제 지수가 1입니다.
  • 0.1의 가수부는 1001 1001 1001...이 끝없이 반복되다가 23비트에서 잘립니다. 0.1은 2진수로 정확히 나누어떨어지지 않는 수이기 때문입니다.
  • 그래서 0.1 + 0.2가 정확히 0.3이 되지 않고, 32비트로 저장한 0.1을 다시 꺼내 보면 0.10000000149...입니다.

장점과 단점 ​

  • 장점: 같은 비트 수로 표현할 수 있는 수의 범위가 훨씬 넓습니다. 32비트 float는 대략 10의 마이너스 38제곱부터 10의 38제곱까지 표현할 수 있습니다. 그래서 현재 대부분의 시스템에서 실수를 다룰 때 이 방식을 씁니다.
  • 단점: 오차가 발생할 수 있습니다. 이유는 두 가지입니다.
    • 가수부 비트 수가 정해져 있어서, 그보다 긴 숫자는 반올림되어 잘립니다.
    • 10진수로는 딱 떨어지는 0.1 같은 수도 2진수로는 무한히 반복되는 소수라 정확히 저장할 수 없습니다.
    • 또 수가 클수록 표현 가능한 값 사이의 간격도 넓어집니다. 큰 수와 아주 작은 수를 더하면 작은 수가 무시되기도 합니다.

언제 무엇을 쓸까 ​

고정 소수점을 쓰는 상황 ​

  1. 임베디드 시스템과 마이크로컨트롤러: 메모리와 처리 능력이 제한된 환경에서는 고정 소수점 연산이 일반적입니다. 부동 소수점 연산을 지원하는 하드웨어(FPU)가 없거나, 있더라도 배터리나 다른 자원을 많이 쓸 수 있기 때문입니다.
  2. 실시간 시스템: 실행 시간을 예측할 수 있어야 하는 프로그램에서는 고정 소수점이 선호됩니다. 부동 소수점 연산은 아주 작은 수(비정규화 수)를 만날 때 등 경우에 따라 실행 시간이 달라질 수 있기 때문입니다.
  3. 비용에 민감한 하드웨어: 부동 소수점 연산 장치를 넣으면 비용이 더 들 수 있어서, 가격을 낮추려고 고정 소수점을 쓰기도 합니다.
  4. 디지털 신호 처리(DSP): 일부 신호 처리 알고리즘은 값의 범위가 정확히 정해져 있어서 고정 소수점으로 충분한 경우가 많습니다.
  5. 돈 계산: 금액은 0.1원 오차도 문제가 되므로, 부동 소수점 대신 "원 단위 정수"나 "센트 단위 정수"로 저장하거나 Python의 decimal처럼 10진수를 그대로 다루는 방식을 씁니다. 소수점 자리를 고정해 두는 것이라 고정 소수점과 같은 발상입니다.

부동 소수점을 쓰는 상황 ​

  1. 과학 계산: 넓은 범위의 값과 높은 정밀도가 필요한 과학·공학 계산
  2. 3D 그래픽스: 3D 모델링처럼 아주 큰 값과 작은 값을 함께 다뤄야 하는 그래픽 작업
  3. 금융 분석: 복잡한 금융 모델링, 위험 평가, 통계 분석처럼 근삿값으로 충분하고 범위가 넓은 계산. 다만 위에서 말했듯 실제 금액을 저장하고 정산하는 계산에는 부동 소수점을 쓰지 않습니다.
  4. 컴퓨터 시뮬레이션: 물리 시스템 시뮬레이션처럼 넓은 범위의 값과 높은 정밀도가 필요한 계산

결론 ​

  • 고정 소수점은 주로 자원이 제한적이고, 값의 범위가 좁고, 정밀도 요구가 크지 않은 환경에서 씁니다.
  • 부동 소수점은 넓은 범위와 높은 정밀도가 필요한 복잡한 계산에 적합합니다.
  • 요즘 프로세서는 부동 소수점 연산도 매우 빨라져서, 예전만큼 둘 사이의 성능 차이가 크지 않습니다.
  • 실수 비교는 a == b 대신 "차이가 아주 작은지"로 비교하는 것이 안전합니다. (Python의 math.isclose)

정리 ​

개념비전공자 버전 요약
고정 소수점소수점 자리를 미리 정해 둠. 단순하고 빠르지만 범위가 좁음
부동 소수점유효 숫자와 "소수점 이동 칸 수"를 따로 저장. 범위가 넓음
가수유효 숫자 부분 (1.23 × 10의 5제곱에서 1.23)
지수소수점을 몇 칸 옮길지 (위 예에서 5)
IEEE 754부동 소수점 저장 방식의 국제 표준. float는 1 + 8 + 23비트
부동 소수점 오차0.1처럼 2진수로 딱 떨어지지 않는 수는 근삿값으로 저장됨
돈 계산부동 소수점 대신 정수(원·센트 단위)나 decimal 사용