소수점 연산 오차 관련 (Epsilon, Approximately, IsNearlyEqual)
컴퓨터는 0과 1, 즉 2진수로 숫자를 처리.
실수의 소수점 숫자를 2진수로 정확히 표현할 수 없는 경우가 있기 때문에(비트가 무한히 반복) 소수점 연산을 할 때에는 미세한 오차가 발생할 수 밖에 없음.
예시를 보면
// C#
using System;
class Program {
static void Main() {
float num1 = 0.1f;
float num2 = 0.2f;
float sum = num1 + num2;
Console.WriteLine("Sum: " + sum);
}
}
// C++
#include <iostream>
int main() {
float num1 = 0.1f;
float num2 = 0.2f;
float sum = num1 + num2;
std::cout << "Sum: " << sum << std::endl;
return 0;
}
위 샘플 코드의 결과는 아래와 같음.
Sum: 0.300000012
0.3 으로 정확히 딱 맞아떨어지지 않고 0.000000012 오차가 발생함. 왜 이런 오차가 발생하냐면, 실수 0.3 을 2진수인 비트 표현으로 바꿔보면
0.010011001100110011001100110011001100110011001100110011001100...
이렇게 특정 비트가 무한히 반복.
그래서 소수점 계산을 할 때는 충분히 작은 숫자를 허용오차(Tolerance), 앱실론(Epsilon) 으로 정의해서 계산 결과를 검사할 때 활용. 아니면, 여러 엔진들이 제공하는 라이브러리 유틸리티를 활용.
Unity Engine 의 경우, Mathf.Approximately() 함수를 활용.
Unreal Engine 의 경우, FMath::IsNearlyEqual() 함수를 활용.
아래는 예시 코드.
using UnityEngine;
public class FloatingPointErrorExample : MonoBehaviour
{
void Start()
{
float a = 0.1f;
float b = 0.2f;
float sum = a + b;
if (Mathf.Approximately(sum, 0.3f))
{
Debug.Log("Sum is approximately 0.3");
}
else
{
Debug.Log("Sum is not approximately 0.3");
}
}
}
//
#include "MyClass.h"
#include "Math/UnrealMathUtility.h"
void UMyClass::FloatingPointErrorExample()
{
float a = 0.1f;
float b = 0.2f;
float sum = a + b;
if (FMath::IsNearlyEqual(sum, 0.3f, KINDA_SMALL_NUMBER))
{
UE_LOG(LogTemp, Warning, TEXT("Sum is approximately 0.3"));
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Sum is not approximately 0.3"));
}
}
언리얼엔진은 소스코드가 공개돼있어서 이와 관련된 매크로 상수들과 코드를 볼 수 있는데 아래와 같다.
// UnrealMathUtility.h 파일
#define UE_SMALL_NUMBER (1.e-8f)
#define UE_KINDA_SMALL_NUMBER (1.e-4f)
#define UE_BIG_NUMBER (3.4e+38f)
#define UE_DOUBLE_SMALL_NUMBER (1.e-8)
#define UE_DOUBLE_KINDA_SMALL_NUMBER (1.e-4)
#define UE_DOUBLE_BIG_NUMBER (3.4e+38)
/**
* Checks if two floating point numbers are nearly equal.
* @param A First number to compare
* @param B Second number to compare
* @param ErrorTolerance Maximum allowed difference for considering them as 'nearly equal'
* @return true if A and B are nearly equal
*/
UE_NODISCARD static FORCEINLINE bool IsNearlyEqual(float A, float B, float ErrorTolerance = UE_SMALL_NUMBER)
{
return Abs<float>( A - B ) <= ErrorTolerance;
}
Unity Engine 의 Mathf.Approximately() 함수는 소스가 공개돼있지 않지만, 추정하자면 아래와 같을 것
using UnityEngine;
public static class MyMath
{
public static bool MyApproximately(float a, float b)
{
float epsilon = 1e-6f; // 미세한 오차 허용값
return Mathf.Abs(a - b) < epsilon;
}
}
Unity Engine 의 소수점 비교를 위한 함수
Mathf.Approximately()
https://docs.unity3d.com/2023.2/Documentation/ScriptReference/Mathf.Approximately.html
Unreal Engine 의 소수점 비교 함수
FMath::IsNearlyEqual()
https://docs.unrealengine.com/5.2/en-US/API/Runtime/Core/Math/FMath/IsNearlyEqual/1/