CS/개발상식

고정 소수점 VS 부동 소수점

glorypang 2025. 6. 9. 15:56
728x90
반응형
SMALL

0. 들어가며

우리가 일상에서 사용하는 숫자와 컴퓨터가 이해하는 숫자는 어떻게 다를까?
컴퓨터는 모든 정보를 0과 1로만 이해하는데, 어떻게 복잡한 수와 소수를 표현할 수 있을까?
컴퓨터의 자료 표현 방식에 대해 자세히 알아보자.


1. 기본 진법 변환

1.1 일상 vs 컴퓨터의 숫자 표현

우리는 보통 10진수를 사용하지만, 컴퓨터는 2진수를 기본으로 한다.

예를 들어 `123`이라는 숫자를 다양한 진법으로 표현하면:

  • 16진수: 7B
  • 10진수: 123 (우리가 사용하는 방식)
  • 8진수: 173
  • 2진수: 1111 011 (컴퓨터가 사용하는 방식)

1.2 2진수로 숫자 표현하기

컴퓨터가 숫자를 2진수로 표현하는 방법을 살펴보자.

정수 변환 예시:

  • 13 = 8 + 4 + 1 → `1101`
  • 각 자리는 2의 거듭제곱(8, 4, 2, 1)을 나타냄

소수 변환 예시:

  • 0.75 = 0.5 + 0.25 → `0.11`
  • 소수점 이하는 2의 음의 거듭제곱(0.5, 0.25, 0.125...)을 나타냄

1.3 실수의 2진수 표현 과정

정수부와 소수부 분리 변환

10진수 실수를 2진수로 변환할 때는 정수부와 소수부를 나누어 각각 변환

 

예시: 11.765625를 2진수로 변환

  1. 정수부 변환 (11 → 2진수)
    • 11 ÷ 2 = 5 나머지 1
    • 5 ÷ 2 = 2 나머지 1
    • 2 ÷ 2 = 1 나머지 0
    • 1 ÷ 2 = 0 나머지 1
    • 결과: `1011`
  2. 소수부 변환 (0.765625 → 2진수)
    • 0.765625 × 2 = 1.53125 → 정수부 1
    • 0.53125 × 2 = 1.0625 → 정수부 1
    • 0.0625 × 2 = 0.125 → 정수부 0
    • 0.125 × 2 = 0.25 → 정수부 0
    • 0.25 × 2 = 0.5 → 정수부 0
    • 0.5 × 2 = 1.0 → 정수부 1
    • 결과: `0.110001`
  3. 최종 결과: `1011.110001`

2진수 무한 소수 문제

모든 10진수 실수가 깔끔하게 2진수로 변환되는 것은 아님!

예시: 0.1을 2진수로 변환

  • 0.1 × 2 = 0.2 → 0
  • 0.2 × 2 = 0.4 → 0
  • 0.4 × 2 = 0.8 → 0
  • 0.8 × 2 = 1.6 → 1
  • 0.6 × 2 = 1.2 → 1
  • 0.2 × 2 = 0.4 → 0 (반복 시작!)
  • ...

결과: `0.000110011001100...` (0011의 무한 반복!)

💡 팁: 소수의 끝이 5가 아닌 수를 2진수로 표현할 경우 대부분 무한 소수가 발생

이렇게 10진수에서 2진수로 소수를 변환하는 것은 많은 경우 정확한 변환이 불가능하며,
컴퓨터는 가장 근사한 값을 저장

→이것이 바로 부동소수점 오차의 근본 원인!


브라우저 콘솔창에서 `console.log(0.1 + 0.2);` 을 입력해보자.

console.log(0.1 + 0.2);
// 0.30000000000000004
console.log(0.1 + 0.2 === 0.3);
// false

2. 실수의 메모리 표현

컴퓨터 메모리는 2진수 체계를 기반으로 데이터를 저장하므로,
당연히 실수도 2진수로 표현해야 함.
정수에 비해 상대적으로 복잡하며, 대표적으로 두 가지 방식이 있음

2.1 고정 소수점(Fixed Point) 방식

동작 원리

메모리를 정수부와 소수부로 고정 분할하여 처리하는 방식

정수부가 15비트라면:

  • `-2^15 ~ 2^15` 만큼의 정수만 표현
  • 소수 부분까지 합친다면 `-2^15.xxx ~ 2^15.xxx` 범위를 표현

예시: 5.625를 고정 소수점으로 표현

  1. 2진수 변환
    • 정수부: 5 → `101`
    • 소수부: 0.625 → `0.101`
    • 전체: `101.101`
  2. 메모리 할당 (32비트 가정: 부호 1비트, 정수부 15비트, 소수부 16비트) 
    부호 정수부 (15비트)  소수부 (16비트)
    0 (0이면 양수, 1이면 음수) 00000000 0000101 10100000 00000000
    • 정수 부분을 늘리면 소수 부분이 줄어들고, 소수 부분을 늘리면 정수 부분이 줄어들게 된다.

 

장단점 분석

✅ 장점:

  • 직관적이고 구현이 단순함
  • 연산 속도가 빠름

❌ 단점:

  1. 표현 범위 제한
    • 32비트 기준으로 정수부 15비트 사용 시
    • 최대 표현 가능한 수: 2^15 - 1 = 32,767
    • 40000.01 같은 수는 표현 불가능! (정수부 15비트로 표현 불가능)
  2. 공간 낭비 심각
    • 22777.12 표현 시 → 0.12라는 작은 소수를 위해 16비트 전체 사용
    • 정수부가 큰 수와 소수부가 큰 수의 효율성 차이 극심

이러한 문제들 때문에 현대 컴퓨터는 부동 소수점 방식을 채택

 

2.2 부동 소수점 (Floating Point) 방식

IEEE 754 표준 구성

현재 컴퓨터의 표준으로, 소수점이 "떠다니는" 방식

IEEE 754표준:

  • 1bit, 8bit, 23bit에 각각 부호, 지수, 가수를 할당하는 방법

32비트 구성:

  • 부호 비트 (1비트): 양수(0), 음수(1)
  • 지수 비트 (8비트): 수의 크기 범위 결정
  • 가수 비트 (23비트): 수의 정밀도 결정

 

표현 방식 이해하기

예시: 263.3을 부동소수점으로 표현

  1. 2진수 변환: `100000111.010011001100110...`
  2. 정규화: 맨 앞 1 뒤로 소수점 이동
    • `1.00000111010011001100110... × 2^8`
    • 소수점을 이동시키는 것을 정규화(Normalization) 이라고 부른다.
    • 정규화는 2진수를 `1.xxxx... X 2^n` 꼴로 변환하는 것
  3. 비트 할당:
    • 부호: `0` (양수)
    • 지수: `10000111` (127 + 8 = 135)
    • 가수: `00000111010011001100110`

 

지수부의 bias 

왜 127을 더할까?

  • 우리가 알고 있는 `2^8`, `2^-3` 같은 지수는 양수/음수 다 있음
  • 그런데 컴퓨터는 부호 없는 8비트로 지수만 저장하려고 함
  • 그래서 음수 지수도 양수처럼 보이게 하기 위해 127을 더해서 저장하는 것!

그래서:

  • 지수의 값은 2^(비트 수) 만큼 표현될 수 있음 (8비트 기준, 2^8 = 256)
  • 그 범위를 절반으로 자르고, 0 한개를 제외한 값을 bias로 지정
  • (8비트 기준) `(2^8 / 2) - 1 = 127`

 

단정밀도(single-precision)

  • 32비트(1비트 부호, 8비트 지수, 23비트 가수)
  • bias 고정값은 127이다.

배정밀도(double-precision)

  • 64비트(1비트 부호, 11비트 지수, 52비트 가수)
  • bias 고정값은 1023이다.

예시:

예: 우리가 표현하려는 실수

263.3 = 1.00000111... × 2^8   ← 지수가 8

그럼 지수 8을 저장해야 되는데, 지수 칸(8비트)은 0~255까지만 저장 가능

그래서...

지수 저장 값 = 실제 지수 + 127 = 8 + 127 = 135

즉, 지수 8을 표현하려면 135를 저장해야 함

이걸 2진수로 바꾸면:

135 = 1000 0111 (8비트)

이게 바로 지수 필드에 들어가는 값

 

즉:

실제 지수  저장 값
-3 124
0 127
8 135

 

부동소수점의 효율성

// Java 타입 범위 비교
long maxLong = 9223372036854775808L;// 8바이트 정수형
float maxFloat = 3.4e38f;// 4바이트 실수형

System.out.println("Long 최대값: " + maxLong);
// 9,223,372,036,854,775,808

System.out.println("Float 최대값: " + maxFloat);
// 3.4 × 10^38

System.out.println("Float이 더 큰가? " + (maxFloat > maxLong));
// true!

놀라운 결과: 4바이트 float이 8바이트 long보다 훨씬 큰 범위를 표현

왜 이런 일이 가능할까?

  1. 고정 소수점의 한계
    • 정수부(15비트) + 소수부(16비트)로 분할
    • 각각 독립적으로 제한된 범위만 표현
  2. 부동 소수점의 효율성
    • 가수부(23비트)에 실제 수의 값 저장
    • 지수부(8비트)로 범위를 기하급수적으로 확장
    • 정수부/소수부 구분 없이 전체 실수를 효율적 표현

부동 소수점의 한계

부동 소수점을 32비트, 64비트, 128비트 등..

비트 수를 더 늘려서, 실수를 더 정밀하게 표현할 수 있음

 

하지만, 무한소수처럼 부동 소수점의 표현법이나 2진수로 표현할 수 없는 값들도 존재

 

역으로 풀어보기

[ 0 10000010 0011011000000000]

가수부: 1.0011011 (생략된 1 포함)

편향된 지수값이 130이므로 실제 지수값은 3

즉, 1001.1011

결과 : 9.6875

 

728x90
반응형
LIST