프로그래밍 이야기

소수점 연산 오차 관련 (Epsilon, Approximately, IsNearlyEqual)

원소랑 2023. 8. 31. 16:47
728x90

컴퓨터는 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

 

Unity - Scripting API: Mathf.Approximately

Floating point imprecision makes comparing floats using the equals operator inaccurate. For example, (1.0 == 10.0 / 10.0) might not return true every time. Approximately() compares two floats and returns true if they are within a small value (Epsilon) of e

docs.unity3d.com

 

Unreal Engine 의 소수점 비교 함수

FMath::IsNearlyEqual()

https://docs.unrealengine.com/5.2/en-US/API/Runtime/Core/Math/FMath/IsNearlyEqual/1/

 

FMath::IsNearlyEqual

 

docs.unrealengine.com

 

728x90
반응형