
선행 글 : https://3dmpengines.tistory.com/2447
부동 소수점의 표현(6) - 반올림 오차 해결 방법들[1]
선행글 : https://3dmpengines.tistory.com/2445 부동 소수점의 표현(4) - 반올림 오차(Rounding Error)이 글이 선행 되어야 합니다https://3dmpengines.tistory.com/2444 용어흡수(Absorption)의미작은 값을 더했지만 결과에
3dmpengines.tistory.com
반올림 오차 해결 방법 비교
| 방법 | 원리 | 효과 |
| ① double 사용 | 가수부 비트 수 증가 | 정밀도 향상 |
| ② 정수·고정소수점 사용 | 정수 단위로 변환해 계산 | 특정 소수 계산을 정확하게 처리 |
| ③ 작은 수부터 합산 | 덧셈 순서 변경 | 오차 감소 가능 |
| ④ Kahan Summation | 덧셈에서 손실된 값을 보정 | 누적 오차 감소 |
| ⑤ 값의 크기 조절 | 큰 값과 작은 값의 차이를 줄임 | 정밀도 손실 감소 |
| ⑥ 허용 오차 비교 | 일정 오차 범위 내에서 비교 | 비교 오류 방지 |
에서 4번가지 이전 글에서 봤다
⑤ 값의 크기 조절 (Scaling / Origin Rebasing)
1. 원리
부동소수점 float는 숫자가 커질수록 인접한 표현 가능 숫자 사이의 간격이 커집니다.
예를 들어 다음 코드를 보겠습니다.
float Position = 1000000.0f;
Position += 0.001f;
1000000.0f 근처에서 인접한 float 값의 간격은 0.0625입니다.
| 구분 | 값 |
| 현재 float 값 | 1000000.00000 |
| 반올림 경계 | 1000000.03125 |
| 다음 float 값 | 1000000.06250 |
| 더하려는 값 | 0.00100 |
0.001은 반올림 경계인 0.03125보다 작으므로 결과가 변하지 않습니다.
// 실제 결과
Position == 1000000.0f;
이 현상을 **흡수 현상(Absorption)**이라고 합니다.
2. 해결 방법: 큰 기준값과 작은 상대값을 분리
값의 크기를 조절하는 방법 중 하나가 큰 기준 좌표를 분리하고, 작은 지역 좌표를 사용하여 계산하는 것입니다.
게임에서 캐릭터가 월드 좌표 1000000.0에 있다고 가정하겠습니다.
기존 방식
float WorldPosition = 1000000.0f;
// 캐릭터 이동
WorldPosition += 0.001f;
// 결과: 1000000.0
캐릭터가 0.001만큼 이동하려 했지만 흡수 현상으로 인해 위치가 변경되지 않습니다.
개선 방식
// 큰 기준 좌표는 double로 저장
double WorldOrigin = 1000000.0;
// 실제 이동 계산은 작은 지역 좌표로 수행
float LocalPosition = 0.0f;
// 캐릭터 이동
LocalPosition += 0.001f;
// 월드 좌표가 필요할 때 변환
double WorldPosition =
WorldOrigin + static_cast<double>(LocalPosition);
각 변수는 다음과 같습니다.
| 변수 | 값 | 역할 |
| WorldOrigin | 1000000.0 | 큰 기준 좌표 |
| LocalPosition | 0.001 | 작은 지역 좌표 |
| WorldPosition | 약 1000000.001 | 두 값을 합친 월드 좌표 |
LocalPosition은 0에 가까운 값이기 때문에 0.001f를 충분히 표현할 수 있습니다.
또한 월드 좌표로 변환할 때는 double로 계산하기 때문에 작은 이동량이 유지됩니다. 단, 0.001f 자체도 정확한 십진수 0.001은 아니므로 결과는 근삿값입니다.

3. 언제 유용한가?
-
오픈월드에서 캐릭터가 월드 원점에서 매우 멀리 이동한 경우
-
넓은 맵에서 월드 좌표를 float로 관리하는 경우
-
큰 좌표에 작은 이동량을 반복적으로 더해야 하는 경우
-
원점 재설정(Origin Rebasing) 또는 지역 좌표 기반 시스템을 설계할 때
주의: 1000000.0f와 0.001f를 둘 다 1000으로 나누는 것처럼 단순히 동일한 배율로 조절하는 것만으로는 정밀도가 개선되지 않을 수 있습니다. 핵심은 큰 기준값을 분리하여 작은 값으로 연산하는 것입니다.
⑥ 허용 오차 비교 (Epsilon Comparison)
1. 원리
부동소수점은 일부 십진수를 정확하게 표현할 수 없기 때문에 수학적으로 같은 값이라도 실제 계산 결과가 다르게 나타날 수 있습니다.
예를 들어 다음 코드를 보겠습니다.
double A = 0.1 + 0.2;
double B = 0.3;
if (A == B)
{
printf("같다\n");
}
else
{
printf("다르다\n");
}
일반적인 IEEE 754 환경에서 결과는 다음과 같습니다.
다르다
실제 저장되는 값은 대략 다음과 같습니다.
| 변수 | 실제 값 |
| A | 0.30000000000000004 |
| B | 0.29999999999999999 |
수학적으로는 둘 다 0.3이지만, 부동소수점에서는 표현과 연산 과정의 반올림 오차 때문에 차이가 발생합니다.
2. 해결 방법: 허용 오차 범위 안이면 같다고 판단
두 값의 차이가 충분히 작다면 같은 값으로 취급합니다.

여기서 Epsilon은 허용할 오차입니다.
#include <cmath>
double A = 0.1 + 0.2;
double B = 0.3;
double Epsilon = 0.000001;
if (std::abs(A - B) <= Epsilon)
{
printf("같다\n");
}
else
{
printf("다르다\n");
}
계산해 보면

이고,

이므로 같다가 출력됩니다.
3. 게임에서의 예시: 캐릭터의 목표 위치 도착 여부
캐릭터의 현재 위치와 목표 위치가 다음과 같다고 가정하겠습니다.
float CurrentPosition = 99.9999f;
float TargetPosition = 100.0f;
기존 방식: 정확히 같은지 비교
if (CurrentPosition == TargetPosition)
{
// 도착 처리
}
두 값이 정확히 일치하지 않으므로 도착 판정이 발생하지 않습니다.
개선 방식: 허용 오차로 비교
#include <cmath>
float Epsilon = 0.001f;
if (std::abs(CurrentPosition - TargetPosition) <= Epsilon)
{
// 도착 처리
}
두 값의 차이는 약 0.0001입니다.

따라서 도착으로 판정합니다.
이 예제에서 Epsilon은 부동소수점 비교 오차를 고려하는 동시에 게임플레이에서 허용하는 도착 거리이기도 합니다.
4. Epsilon은 어떻게 정해야 하는가?
허용 오차는 계산하려는 값의 크기와 요구 정확도에 맞게 정해야 합니다.
예를 들어 값이 1000000.0f 정도라면 float의 인접 값 간격은 0.0625입니다.
이런 상황에서 무조건 Epsilon = 0.000001f를 사용한다면, float의 실제 표현 간격보다 훨씬 작은 값을 비교 기준으로 삼게 됩니다.
또한 다음과 같이 상대 오차를 함께 고려할 수 있습니다.
#include <algorithm>
#include <cmath>
bool NearlyEqual(double A, double B)
{
const double AbsTolerance = 1e-12;
const double RelTolerance = 1e-9;
double Difference = std::abs(A - B);
double Scale = std::max(std::abs(A), std::abs(B));
return Difference <=
std::max(AbsTolerance, RelTolerance * Scale);
}
위 허용 오차는 예시이며, 실제 프로그램의 요구 정확도에 따라 조절해야 합니다.
허용 오차 비교는 어디까지나 비교 방식의 개선이지, 손실된 부동소수점 정밀도를 복원하는 기술은 아닙니다.
앞서 설명한 float 값이 1000000.0f이고, 인접한 float 값의 간격이 0.0625인 상황이라면, 허용 오차(Epsilon)는 비교 목적에 따라 결정해야 합니다.
1. float의 정밀도를 기준으로 정한다면
1000000.0f 주변에서 표현 가능한 값은 다음과 같습니다.
| 현재 값 | 1000000.00000 |
| 중간값 | 1000000.03125 |
| 다음 float 값 | 1000000.06250 |
| 인접 값 간격(1 ULP) | 0.06250 |
인접한 float 값까지 같은 값으로 취급하려는 목적이라면 **Epsilon = 0.0625f**를 사용할 수 있습니다.
float A = 1000000.0f;
float B = 1000000.0625f;
float Epsilon = 0.0625f;
if (std::abs(A - B) <= Epsilon)
{
// 허용 오차 이내
}
다만 이 경우 실제로 구별되는 두 float 값도 같다고 판단합니다. 따라서 무조건 좋은 설정은 아닙니다.
특히 Epsilon = 0.03125f처럼 인접 간격보다 작게 설정하면 이 주변의 서로 다른 정상적인 float 값들은 같다고 판단되지 않습니다.
2. 게임 캐릭터의 목표 위치 비교라면?
앞서 사용한 예시가 다음과 같았습니다.
float CurrentPosition = 99.9999f;
float TargetPosition = 100.0f;
이 경우 두 값의 차이는 약 0.0001입니다.
목표 위치에 0.001 이내로 접근하면 도착한 것으로 처리하려는 게임이라면 다음처럼 설정하면 됩니다.
constexpr float ArrivalTolerance = 0.001f;
if (std::abs(CurrentPosition - TargetPosition)
<= ArrivalTolerance)
{
// 도착 처리
}
여기서는 float 표현 간격보다 게임에서 허용할 위치 오차가 더 중요한 기준입니다.
3. 상황별 권장 기준
| 상황 | 권장 기준 |
| 1000000 근처에서 인접 float 값까지 같은 값으로 취급 | 0.0625 (1 ULP) |
| 99.9999와 100.0의 위치 도착 판정 | 0.001 (예시) |
| 일반적인 수치 비교 | 절대 오차 + 상대 오차 고려 |
중요한 점: 1000000.0f + 0.001f에서 발생하는 흡수 현상은 Epsilon을 조절해도 해결되지 않습니다. 이미 0.001이 손실되었기 때문입니다.
따라서 큰 월드 좌표의 작은 이동량을 정확히 처리하려는 상황이라면 Epsilon을 키우기보다 double 또는 지역 좌표(Origin Rebasing)를 사용하는 것이 적절합니다.
99.9999와 100.0의 위치 도착 판정 이 말은
캐릭터가 목표 위치인 100.0에 정확히 도달하지 않아도, 목표 위치와의 거리가 0.001 이하라면 도착한 것으로 판단한다는 뜻입니다.
1. 예시 상황
캐릭터가 X축을 따라 이동하고 있다고 가정하겠습니다.
float CurrentPosition = 99.9999f; // 현재 위치
float TargetPosition = 100.0f; // 목표 위치
float Epsilon = 0.001f; // 도착 허용 거리
두 위치의 차이를 계산합니다.

설정한 허용 오차는 0.001입니다.

따라서 캐릭터가 목표 위치에 도착했다고 판단합니다.
2. Epsilon에 따라 도착 판정이 달라짐
| 현재 위치 | 목표 위치와의 거리 | Epsilon = 0.001 |
| 99.9900 | 0.0100 | 미도착 |
| 99.9990 | 약 0.0010 | 경계 부근* |
| 99.9995 | 약 0.0005 | 도착 |
| 99.9999 | 약 0.0001 | 도착 |
| 100.0000 | 0 | 도착 |
* 99.9990f는 실제 float 표현 오차 때문에 계산된 거리가 정확히 0.001이 아닐 수 있어 경계 판정에 주의해야 합니다.
3. C++ 구현
#include <cmath>
float CurrentPosition = 99.9999f;
float TargetPosition = 100.0f;
float Epsilon = 0.001f;
if (std::abs(CurrentPosition - TargetPosition) <= Epsilon)
{
// 목표 위치에 도착
}
else
{
// 아직 도착하지 않음
}
4. 중요한 차이
여기서 Epsilon = 0.001f는 float 자체의 반올림 오차가 0.001이라는 뜻이 아닙니다.
게임 개발자가 목표 위치에서 0.001 이내에 들어오면 도착했다고 인정하겠다는 기준을 직접 정한 것입니다.
예를 들어 게임의 좌표 단위가 1m라면 0.001은 1mm를 의미합니다.
즉, 부동소수점의 수치 오차를 고려한 비교와 게임플레이에서 허용하는 도착 거리 판정은 서로 관련은 있지만 엄밀히는 다른 개념입니다.
통상적으로 Epsilon 은 어떻게 잡는게 좋은가?
통상적으로 Epsilon은 하나의 고정값으로 정하기보다, 비교하는 값의 크기와 허용 가능한 오차에 따라 설정하는 것이 좋습니다.
게임 프로그래밍에서는 크게 세 가지 기준을 사용합니다.
1. 일반적인 Epsilon 설정 기준
| 방식 | 설정 기준 | 사용 상황 |
| 절대 오차 | 0.0001f 등 고정값 | 값의 범위가 일정하거나 거리·시간의 허용 범위가 명확한 경우 |
| 상대 오차 | 비교 값의 크기에 비례 | 작은 값부터 큰 값까지 폭넓게 비교하는 경우 |
| 절대 + 상대 오차 | 두 기준을 함께 사용 | 일반적인 수치 계산의 근사 비교 |
참고로 float의 머신 엡실론은 약 1.1920929 × 10⁻⁷입니다.
하지만 이 값은 1.0 근처에서의 표현 간격을 나타내며, 모든 크기의 숫자에 적용할 고정 허용 오차가 아닙니다.
2. 통상적으로 많이 사용하는 방법: 절대 오차 + 상대 오차
#include <algorithm>
#include <cmath>
bool NearlyEqual(float A, float B,
float AbsEpsilon = 0.00001f,
float RelEpsilon = 0.000001f)
{
// ① 두 값의 차이
float Diff = std::abs(A - B);
// ② 두 값 중 절댓값이 더 큰 값
float Scale = std::max(std::abs(A), std::abs(B));
// ③ 허용할 오차 범위
float Tolerance =
std::max(AbsEpsilon, RelEpsilon * Scale);
// ④ 차이가 허용 오차 이내라면 같다고 판단
return Diff <= Tolerance;
}
AbsEpsilon : 값의 크기와 관계없이 적용할 최소 허용 오차
RelEpsilon : 값의 크기에 비례해서 허용할 오차 비율
이 코드에서 0.00001f, 0.000001f는 출발점으로 사용할 수 있는 예시값이지, 모든 게임에 적용할 표준값은 아닙니다.
왜 RelEpsilon에 Scale을 곱할까?
RelEpsilon = 0.000001이라는 것은 비교하는 값 크기의 100만 분의 1까지 차이를 허용하겠다는 의미입니다.
백분율로는 0.0001%입니다.
따라서 실제 허용 오차를 계산하려면 비교하는 값의 크기를 곱해야 합니다.

예를 들어 RelEpsilon = 0.000001을 그대로 사용하면서 A와 B의 크기만 변경해 보겠습니다.
| Scale | RelEpsilon × Scale | 허용 오차 |
| 1 | 0.000001 × 1 | 0.000001 |
| 100 | 0.000001 × 100 | 0.0001 |
| 1,000 | 0.000001 × 1,000 | 0.001 |
| 1,000,000 | 0.000001 × 1,000,000 | 1.0 |
즉, 비교하는 숫자가 커지면 허용 오차도 커지는 구조입니다.
이렇게 하는 이유는 float의 절대적인 표현 오차도 일반적으로 숫자의 크기에 따라 커지기 때문입니다.
다만 이 비율은 개발자가 정하는 것이지, float가 반드시 그만큼 오차를 낸다는 뜻은 아닙니다.
'프로그래밍(Programming) > c++, 11, 14 , 17, 20' 카테고리의 다른 글
| 부동 소수점의 표현(6) - 반올림 오차 해결 방법들[1] : 6가지와 Kahan Summation(카한 합산법) (0) | 2026.11.05 |
|---|---|
| 부동 소수점의 표현(5) - 흡수 현상(Absorption) (0) | 2026.11.04 |
| 부동 소수점의 표현(4) - 반올림 오차(Rounding Error) (0) | 2026.11.04 |
| 부동 소수점의 표현(3) - 실수의 진법 변환 (Real Number Base Conversion) (0) | 2026.11.03 |
| 부동 소수점의 표현(2) - 부동 소수점의 오류:무한 반복 (0) | 2026.11.03 |
