double 반올림에서 발생하는 0.4999…의 저주와 깔끔한 해결법
1. 문제의 시작
C/C++에서 double 값을 반올림할 때, 아래와 같은 코드는 너무 흔하다.
(int)(value + 0.5)
대부분의 경우는 잘 동작한다. 하지만 특정 값, 장시간 실행, 환경 차이가 겹치면 예상과 다른 결과가 로그에 남기 시작한다.
1.5 → 1 2.5 → 2 10.05 → 10.0
이 현상은 흔히 이렇게 불린다.
“0.4999…의 저주”
2. 원인: CPU 버그가 아니라 IEEE 754
이 문제는 Intel CPU 버그가 아니다. AMD, ARM 환경에서도 동일하게 발생할 수 있다.
핵심 원인은 단순하다.
double은 10진 소수를 정확히 표현하지 못한다
예를 들어 다음과 같다.
1.5 → 1.499999999999… 2.5 → 2.499999999999…
그래서 다음 연산이 문제가 된다.
(double)1.5 + 0.5 = 1.999999999999… (int)(1.999999999999…) = 1
수학적으로는 맞아 보이지만, 컴퓨터 내부 표현에서는 틀린 결과가 된다.
3. 기존 방식들의 한계
❌ (value + 0.5) 캐스팅
- 부동소수점 오차에 취약
- 음수까지 고려하면 코드가 복잡해짐
❌ round / lround 계열
- 플랫폼 및 구현 차이
- FPU 반올림 모드 영향 가능
❌ 문자열 기반 반올림
- 성능 저하
- locale / 포맷 영향
- 숫자 연산으로 보기 어려움
4. 핵심 아이디어
문제는 항상 이 지점에서 발생한다.
… 0.499999999999
그래서 해결 전략은 의외로 단순하다.
0.5보다 아주 조금 더 큰 값을 더해준다
이것은 꼼수가 아니라, IEEE 754 환경에서 의도를 정확히 전달하기 위한 보정이다.
5. 깔끔한 해결 코드
#include <cmath>
double MJRound(double value, int pos = 0)
{
// 1. 소수점 이동
double scale = std::pow(10.0, pos);
double shifted = value * scale;
// 2. 0.4999… 문제 방지를 위한 epsilon 보정
double margin = (shifted >= 0.0)
? 0.50000000001
: -0.50000000001;
// 3. 소수점 제거 후 원복
return std::trunc(shifted + margin) / scale;
}
6. 이 방식의 장점
- 양수 / 음수 처리 로직이 단순함
round()계열 함수 의존 없음- FPU 반올림 모드 영향 최소화
- 장시간 실행에서도 결과 안정적
- 코드만 봐도 의도가 명확함
7. 요약
- 이 문제는 특정 산업이나 CPU의 문제가 아니다
- 부동소수점 수학의 구조적 한계
- 반올림에는 epsilon 보정이 필요하다
- 반올림은 생각보다 시스템 레벨 문제에 가깝다
8. 한 줄 결론
double 반올림에서 가장 위험한 건 “이 정도면 되겠지”라는 확신이다.
'I ♥ Programming' 카테고리의 다른 글
| OpenCV로 Shape-Based Geometric Model Find 기능 사용하기 (0) | 2025.06.18 |
|---|---|
| XListCtrl (CXListCtrl) 클래스 (CodeProject 소스코드) (0) | 2025.06.17 |
| 문서 파일을 열고 있는 프로세스 ID 강제 종료 (0) | 2024.12.11 |
| Windows XP SP3용 그림판 EXE 파일 (mspaint.exe) (0) | 2024.06.20 |
| c# 디자인 폼 없어졌을때 조치 (0) | 2022.03.04 |