C# 부동 소수점 연산의 정확도 문제와 해결 방안

김민구·2024년 10월 25일
0

C#

목록 보기
3/31

부동 소수점 표현

  • 컴퓨터는 부동 소수점 표준인 IEEE 754를 사용해 실수를 표현, 실수는 다음과 같이 세 부분으로 나뉜다.
    ㄴ 부호 비트(Sign bit) : 숫자가 양수인지 음수인지 나타냄
    ㄴ 지수(Exponent) : 소수점의 위치를 조정
    ㄴ 가수(Mantissa 또는 Fraction): 실제 숫자 데이터를 나타냄

정밀도 한계

  • 메모리 공간의 제한 때문에 가수 부분에 숫자를 저장할 때 일정한 비트만 사용할 수 있다. 예를 들어, 64비트 부동 소수점 표현(더블 프리시전)에서 가수 부분은 52비트로 제한되며, 이로 인해 숫자를 정확하게 표현하는 데 한계가 된다.

연산 과정의 오차

  • 부동 소수점 연산에서는 이러한 근삿값을 사용
    ㄴ 예를 들어, 1.1 + 0.1을 계산할 때, 각 숫자의 이진수 근삿값을 더하므로, 이 과정에서 미세한 오차가 누적되어, 최종 결과가 1.2와 정확히 일치하지 않을 수 있다.
    ㄴ ex) 1.2, 0.3 등의 적은 소수자리의 숫자를 계산할때에 올바른 출력이 나타나는 이유는 c#에서 ToString을 할때 round off error무시한 반올림 숫자가 반환되기 때문

해결방안

  • 정수 연산 사용
    ㄴ 원리 : 실수 대신 정수를 사용하여 연산을 수행
    ex) 소수점 이하 값을 필요한 자릿수만큼 확대하여 정수로 변환한 후 연산을 수행 (3.14 + 1 = 314 + 100 -> 414 -> 4.14)
    ㄴ 장점 : 정수 연산은 부동 소수점 연산보다 오차가 없고 더 예측 가능
    ㄴ 적용 사례 : 금융 계산에서 소수점 이하의 금액을 정확히 계산해야 할 때 자주 사용

  • 문자열로 표현하기
    ㄴ 두 숫자를 계산할 때 문자열에서 각 자리의 문자를 숫자로 바꾼 뒤 뒤에서부터 한 자리 씩 계산한다.

  • 높은 정밀도 라이브러리 사용
    ㄴ 원리 : 표준 부동 소수점 연산보다 더 많은 비트를 사용하여 실수를 표현하고 연산하는 라이브러리를 사용
    ㄴ 장점 : 매우 높은 정밀도의 연산이 가능
    ㄴ 적용 사례 : 과학적 계산, 큰 수의 정밀한 연산, 복잡한 수학적 모델링 등에 사용

  • float형 보다 double형 변수 사용

    float의 상대오차는 10^(-7)
    double의 상대오차는 10^(-15)

  • Epslion 사용
    ㄴ 부동소수점 연산에서 반올림 오차와 같은 작은 부동소수점 값들을 처리하는 데 사용되는 매우 작은 값이다. 부동소수점 연산의 정확도 한계나 부정확성을 고려하여 사용 할수가 있다.

float a = 2f;
float b = 2f;

float epsilon = 1E-6;	// 1 × 10^-6과 같고 계산하면 0.000001;

if (Math.Abs(a - b) < epsilon)	// Math.Abs()는 괄호 안에 절대값을 반환하는 함수/ a-b의 차이가 epsilon보다 작을때 거의 같은 값이라 할 수 있다.
{
    Console.WriteLine("a and b are approximately equal.");	// a와 b는 거의 같다 출력
}
  • decimal 자료형 사용
    ㄴ CPU 자체에서 지원하는 형식은 아니다.

    표현범위: ± 1.0 x 10 -28 ~ ± 7.9228 x 1028
    정밀도: 28 ~ 29

  • decimal 자료형 사용하기

decimal num1 = 10.1234567778797898989m; //ok
//m 키워드가 없으면 double이고, double형을 decimal에 대입하려 했기 때문에 오류 발생.
decimal num2 = 10.1234567778797898989;
decimal num3 = 10m; //ok
decimal num4 = 10; //ok
  • 접미사 m을 사용한다.
    ㄴ 정수일 때는 묵시적 변환을 허용하기 때문에 안붙여도 된다.
    ㄴ 부동소수점일 때는 명시적 변환만 허용하기 때문에 반드시 붙여야 한다.

  • decimal과 다른 자료형 간의 변환

decimal num1 = 10.1234567778797898989m;
decimal num2 = 10.123456777898989m;
decimal num3 = 10m;

//명시적 변환은 모두 ok
//묵시적 변환은 오류 발생
float num4 = num1; // 컴파일 오류
float num5 = (float)num1; //ok
num2 = num5; //컴파일 오류
num2 = (decimal)num5; //ok

int num6 = num3; //컴파일 오류
int num7 = (int)num3; //ok
num3 = num7; //ok
  • dicimal형이 게임 개발에서 거의 사용되지 않는 이유
    ㄴ 연산속도가 float나 double형에 비해 느리고 더 많은 메모리를 차지한다. 게임은 매 프레임마다 수많은 연산을 수행해야 하기 때문에 연산 속도가 느려 성능 저하가 발생
profile
C#, Unity

0개의 댓글