컴퓨터구조 Part 2
📚 목차
- 2.1 실수의 표현 (40분)
- 2.1.1 고정소수점 표현
- 2.1.2 부동소수점 표현 (IEEE 754 표준)
- 2.1.3 실수 표현의 한계
- 2.2 문자의 표현 (30분)
- 2.2.1 ASCII 코드
- 2.2.2 한글 인코딩의 역사
- 2.2.3 유니코드와 UTF
- 2.3 부동소수점 연산의 오차와 주의사항 (20분)
- 2.3.1 부동소수점 연산의 문제점
- 2.3.2 프로그래밍 시 주의사항
- 2.3.3 실제 사고 사례 분석
2.1 실수의 표현
2.1.1 고정소수점 표현
고정소수점(Fixed-point) 표현은 실수를 정수부와 소수부로 나누어 고정된 위치에 소수점을 둔다. 예를 들어, 16비트를 8.8 형식으로 사용하면 상위 8비트는 정수부, 하위 8비트는 소수부를 표현한다.
게임 프로그래밍이나 임베디드 시스템에서는 부동소수점 연산이 느리거나 불가능한 경우가 있다. 고정소수점은 정수 연산만으로 실수를 다룰 수 있어 매우 빠르다!
실습: 12.75를 8.8 고정소수점으로 표현하기
| 장점 | 단점 | 사용 예 |
|---|---|---|
| 빠른 연산 속도 | 제한된 표현 범위 | 게임 물리 엔진 |
| 예측 가능한 정밀도 | 고정된 정밀도 | 오디오 처리 |
| 정수 ALU만 필요 | 오버플로우 위험 | 임베디드 시스템 |
2.1.2 부동소수점 표현 (IEEE 754 표준) ⭐
부동소수점(Floating-point)은 과학적 표기법처럼 가수부와 지수부를 분리하여 표현한다. IEEE 754는 이를 표준화한 규격으로, 현재 거의 모든 컴퓨터가 사용한다.
IEEE 754 단정밀도(32비트) 구조
- S (1비트): 부호비트 (0=양수, 1=음수)
- E (8비트): 지수부 (바이어스 127 적용)
- M (23비트): 가수부 (정규화된 1.xxx의 xxx 부분)
실제 값 = (-1)^S × 2^(E-127) × (1 + M/2²³)
실습: -12.375를 IEEE 754 단정밀도로 변환
음수이므로 S = 1
12.375₁₀ = 1100.011₂
(12 = 1100₂, 0.375 = 0.011₂)
1100.011₂ = 1.100011 × 2³
따라서 지수 = 3
E = 3 + 127 = 130₁₀ = 10000010₂
1.100011의 소수부분: 100011
23비트로 확장: 10001100000000000000000
특수값 표현
| 값 | 지수 (E) | 가수 (M) | 의미 |
|---|---|---|---|
| ±0 | 00000000 | 모두 0 | 영(Zero) |
| 비정규수 | 00000000 | 0이 아님 | 매우 작은 수 |
| ±∞ | 11111111 | 모두 0 | 무한대 |
| NaN | 11111111 | 0이 아님 | 숫자 아님 |
2.1.3 실수 표현의 한계
컴퓨터는 유한한 비트로 무한한 실수를 표현해야 한다. 이는 필연적으로 정밀도 손실과 표현 불가능한 수를 만들어낸다.
0.1은 왜 정확히 표현할 수 없을까?
0.1을 이진수로 변환하면:
0.1₁₀ = 0.0001100110011001100110011...₂ (무한 반복)
32비트로 저장하면:
0.00011001100110011001101 (반올림됨)
실제 값: 0.100000001490116119384765625
오차: 0.000000001490116119384765625
이처럼 10진수로 간단한 수도 2진수로는 순환소수가 될 수 있다!
Machine Epsilon 이해하기
Machine Epsilon(ε)은 1.0과 그 다음 표현 가능한 수 사이의 간격이다.
- 단정밀도: ε ≈ 1.19 × 10⁻⁷
- 배정밀도: ε ≈ 2.22 × 10⁻¹⁶
이보다 작은 차이는 구별할 수 없다!
2.2 문자의 표현
2.2.1 ASCII 코드
ASCII(American Standard Code for Information Interchange)는 1963년 개발된 7비트 문자 인코딩으로, 128개의 문자를 표현할 수 있다.
ASCII 코드의 구성
| 0~31 | 제어 문자 (줄바꿈, 탭 등) |
| 32~47 | 특수 문자 (공백, !, " 등) |
| 48~57 | 숫자 (0~9) |
| 65~90 | 대문자 (A~Z) |
| 97~122 | 소문자 (a~z) |
실습: "Hello"를 16진수 ASCII로 표현
| 문자 | ASCII (10진수) | ASCII (16진수) | 이진수 |
|---|---|---|---|
| H | 72 | 0x48 | 0100 1000 |
| e | 101 | 0x65 | 0110 0101 |
| l | 108 | 0x6C | 0110 1100 |
| l | 108 | 0x6C | 0110 1100 |
| o | 111 | 0x6F | 0110 1111 |
결과: "Hello" = 0x48 65 6C 6C 6F
대문자를 소문자로 변환하려면 32를 더하면 된다! (A=65, a=97)
이는 비트 연산으로 OR 0x20과 같다.
2.2.2 한글 인코딩의 역사
한글은 초성, 중성, 종성의 조합으로 이루어져 있어 인코딩이 복잡하다. 이를 해결하기 위해 다양한 방식이 개발되었다.
조합형 vs 완성형
| 구분 | 조합형 | 완성형 |
|---|---|---|
| 원리 | 자모를 조합하여 표현 | 완성된 글자에 코드 부여 |
| 장점 | 모든 한글 표현 가능 | 간단한 구현 |
| 단점 | 복잡한 조합 로직 | 일부 글자만 표현 |
| 예시 | 한 = ㅎ(5비트) + ㅏ(5비트) + ㄴ(5비트) | 한 = 0xD55C (고정) |
실습: '가'의 다양한 인코딩 비교
| 인코딩 | 바이트 수 | 16진수 값 | 특징 |
|---|---|---|---|
| EUC-KR | 2 | 0xB0A1 | 한국 표준 |
| CP949 | 2 | 0xB0A1 | 윈도우 확장 |
| UTF-8 | 3 | 0xEAB080 | 국제 표준 |
| UTF-16 | 2 | 0xAC00 | 유니코드 직접 |
한글이 깨져 보이는 이유는 대부분 인코딩 불일치 때문이다. EUC-KR로 저장한 파일을 UTF-8로 읽으면 "한글"이 "�븳湲�"처럼 보인다!
2.2.3 유니코드와 UTF
유니코드는 전 세계 모든 문자를 하나의 체계로 표현하기 위한 국제 표준이다. 각 문자에 고유한 코드 포인트(Code Point)를 부여한다.
UTF-8 인코딩 규칙
| 코드 포인트 범위 | UTF-8 바이트 패턴 | 바이트 수 |
|---|---|---|
| U+0000 ~ U+007F | 0xxxxxxx | 1 |
| U+0080 ~ U+07FF | 110xxxxx 10xxxxxx | 2 |
| U+0800 ~ U+FFFF | 1110xxxx 10xxxxxx 10xxxxxx | 3 |
| U+10000 ~ U+10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | 4 |
실습: '한'(U+D55C)을 UTF-8로 인코딩
'한' = U+D55C = 0xD55C = 54620₁₀
U+0800 ~ U+FFFF 범위 → 3바이트 필요
0xD55C = 1101 0101 0101 1100₂
1110xxxx 10xxxxxx 10xxxxxx
11101101 10010101 10011100
0xED 95 9C = 11101101 10010101 10011100₂
• ASCII와 100% 호환 (영문은 그대로 1바이트)
• 자체 동기화 가능 (중간부터 읽어도 문자 경계 찾기 가능)
• 엔디안 문제 없음 (바이트 순서가 고정)
"Hello 안녕" 인코딩별 크기 비교
| 인코딩 | 바이트 수 | 16진수 표현 |
|---|---|---|
| ASCII | 불가능 | 한글 표현 불가 |
| EUC-KR | 9 | 48 65 6C 6C 6F 20 BE C8 B3 E7 |
| UTF-8 | 12 | 48 65 6C 6C 6F 20 EC 95 88 EB 85 95 |
| UTF-16LE | 16 | 48 00 65 00 6C 00 6C 00 6F 00 20 00 48 C5 55 B1 |
2.3 부동소수점 연산의 오차와 주의사항
2.3.1 부동소수점 연산의 문제점
부동소수점 연산은 빠르고 넓은 범위를 표현할 수 있지만, 정확도 문제로 인해 예상치 못한 결과를 낳을 수 있다.
1. 반올림 오차(Rounding Error): 유한한 비트로 무한소수 표현
2. 취소 오차(Cancellation Error): 비슷한 수의 뺄셈
3. 흡수 오차(Absorption Error): 크기가 매우 다른 수의 덧셈
반올림 오차 실습
// C 코드 예시 float sum = 0.0f; for(int i = 0; i < 10; i++) { sum += 0.1f; } printf("%.20f\n", sum); // 예상: 1.0 // 실제: 0.99999994039535522461
0.1을 10번 더해도 정확히 1.0이 되지 않는다! 각 연산마다 미세한 오차가 누적된다.
취소 오차 실습
정확한 값: 0.000001 (유효숫자 1개)
부동소수점 결과: 0.000001 (정밀도 손실 발생)
원래 6자리 유효숫자가 1자리로 줄어든다! 이를 치명적 취소(Catastrophic Cancellation)라 한다.
흡수 오차 실습
// 단정밀도 부동소수점 float big = 1e10f; // 10,000,000,000 float small = 1.0f; // 1 float result = big + small - big; printf("%f\n", result); // 예상: 1.0 // 실제: 0.0 (작은 수가 "흡수"됨)
2.3.2 프로그래밍 시 주의사항
부동소수점의 한계를 알고 적절한 대응 방법을 적용해야 안전한 프로그램을 만들 수 있다.
실수 비교의 올바른 방법
// ❌ 잘못된 방법 if(a == b) { ... } // ✅ 올바른 방법 #define EPSILON 1e-6 if(fabs(a - b) < EPSILON) { ... } // ✅ 더 정확한 방법 (상대 오차 고려) if(fabs(a - b) < EPSILON * fmax(fabs(a), fabs(b))) { ... }
금융: 센트 단위 정수로 계산 (예: $12.34 → 1234)
게임: 고정소수점 사용 (예: 16.16 형식)
과학계산: 임의 정밀도 라이브러리 사용 (예: GMP, MPFR)
0.1 + 0.2 ≠ 0.3인 이유 (비트 수준 분석)
0.1₁₀ = 0.00011001100110011...₂ (무한 반복) 0.2₁₀ = 0.00110011001100110...₂ (무한 반복) IEEE 754 단정밀도로 저장: 0.1 → 0x3DCCCCCD (실제: 0.10000000149011612) 0.2 → 0x3E4CCCCD (실제: 0.20000000298023224) 합 → 0x3E99999A (실제: 0.30000000447034836) 0.3 → 0x3E999999 (실제: 0.29999999403953552) 차이: 0.00000001043081284 ≠ 0
// ❌ 위험한 코드 for(float i = 0.0; i < 1.0; i += 0.1) { // 10번이 아닌 9번만 실행될 수 있음! } // ✅ 안전한 코드 for(int i = 0; i < 10; i++) { float value = i * 0.1f; // 정수 카운터 사용 }
2.3.3 실제 사고 사례 분석
부동소수점 오차는 단순한 이론적 문제가 아니다. 실제로 인명 피해와 막대한 손실을 초래한 사례들이 있다.
🚀 패트리어트 미사일 실패 (1991년 2월 25일)
상황: 걸프전 중 사우디아라비아 다란
원인: 시스템 시계의 0.1초를 이진수로 변환 시 오차
- 0.1초 = 0.0001100110011...₂ (무한 반복)
- 24비트 레지스터: 0.00011001100110011001100 (잘림)
- 오차: 0.000000095초/틱
- 100시간 작동 후: 0.34초 오차 누적
- 미사일 속도 1,676m/s × 0.34초 = 약 570m 빗나감
결과: 스커드 미사일 요격 실패, 28명 사망, 100명 부상
💻 펜티엄 FDIV 버그 (1994년)
문제: 특정 나눗셈에서 잘못된 결과
4195835.0 / 3145727.0 = ? 정답: 1.33382044913624100 펜티엄: 1.33373906890203759 오차: 0.00008138023420341
원인: 나눗셈 조견표(lookup table) 5개 항목 누락
영향: Intel 4.75억 달러 손실 (칩 교체 비용)
📈 주식거래 시스템 오류 사례
Knight Capital Group (2012년 8월 1일)
- 부동소수점 가격 계산 오류
- 45분 동안 잘못된 거래 실행
- 손실: 4억 6천만 달러
- 회사 파산 위기 (후에 인수됨)
교훈: 금융 시스템은 반드시 정수 연산 사용!
✅ 금전 계산은 항상 정수로
✅ 실수 비교 시 epsilon 사용
✅ 루프 카운터는 정수만
✅ 중요 계산은 double 사용 고려
✅ 테스트 시 경계값 확인
✅ 오차 누적 가능성 항상 염두
요약 및 핵심 정리
핵심 개념 정리
| 주제 | 핵심 내용 | 주의사항 |
|---|---|---|
| 고정소수점 | 정수부.소수부 고정 빠른 연산 |
제한된 범위 오버플로우 주의 |
| IEEE 754 | S(1) + E(8/11) + M(23/52) 넓은 범위 |
정밀도 손실 특수값 처리 |
| ASCII | 7비트, 128문자 영문 중심 |
다국어 불가 확장 필요 |
| UTF-8 | 가변 길이 ASCII 호환 |
한글 3바이트 처리 복잡 |
| 부동소수점 오차 | 반올림/취소/흡수 누적 위험 |
직접 비교 금지 epsilon 사용 |
프로그래밍 베스트 프랙티스
꼭 기억해야 할 10가지
- 실수를 == 로 비교하지 마라
- 금융 계산은 정수로 하라
- 루프 카운터는 정수만 사용하라
- 문자 인코딩을 명시하라
- 0.1 + 0.2 ≠ 0.3임을 기억하라
- NaN은 자기 자신과도 같지 않다
- 큰 수 + 작은 수는 위험하다
- UTF-8이 만능은 아니다
- 고정소수점도 고려하라
- 테스트 시 경계값을 확인하라
더 깊이 공부하기
📚 "What Every Computer Scientist Should Know About Floating-Point Arithmetic" - David Goldberg
📚 "The Unicode Standard" - Unicode Consortium
🔧 IEEE 754 변환기: float.exposed
🔧 문자 인코딩 테스터: r12a.github.io/app-conversion/
'강의 > 컴퓨터 구조' 카테고리의 다른 글
| 데이터의 표현 - Part1 (0) | 2025.09.19 |
|---|---|
| 컴퓨터 구조 개론 - Part 3 (1) | 2025.09.12 |
| 컴퓨터 구조 개론 - Part 2 (0) | 2025.09.12 |
| 컴퓨터 구조 개론 - Part 1 (0) | 2025.09.12 |