Computer >> 컴퓨터 >  >> 프로그래밍 >> C++

C++ 부동 소수점 예외(Floating Point Exception) 완벽 가이드: 원인과 해결 방법

부동 소수점 예외(Floating-Point Exception)는 프로그램이 숫자 값에 대해 부적절한 연산을 수행하려고 할 때 발생하는 오류입니다. 대표적인 발생 사례로는 0으로 나누기, 음수의 제곱근 계산, double 자료형이 표현할 수 있는 범위를 초과하는 연산 등이 있습니다. 사용자가 이러한 유효하지 않은 연산을 시도하면 예외가 발생합니다.

C++에서는 SIGFPE라는 시그널 핸들러가 부동 소수점 예외(FPE)를 처리합니다. 사용자가 위에서 언급한 잘못된 연산을 수행하려고 하면 이 시그널 핸들러가 호출되며, 개입 후 표준 출력으로 오류 메시지를 출력하고 프로그램을 중단시킵니다.

부동 소수점 예외는 왜 발생할까?

부동 소수점 예외는 주로 프로그래밍 오류나 명세(specification) 범위를 벗어난 값을 처리하려는 시도에서 비롯됩니다. 예를 들어 정수를 0으로 나누거나, 음수의 제곱근을 구하려고 하면 부동 소수점 예외가 발생합니다. 또한 일부 경우에는 프로세서의 오작동(오검출)으로 인해 발생하기도 합니다.

부적절한 연산, 언더플로우, 오버플로우, 0으로 나누기, 정밀도 문제 등 다양한 요인이 부동 소수점 예외를 유발할 수 있습니다. 아래에서 하나씩 자세히 살펴보겠습니다.

1. 부적절한 연산 (Illegal Operation)

연산이 수학적으로 정의되지 않았거나 유효하지 않은 값을 다룰 때 프로그램은 실행에 실패합니다. 대표적인 예로 음수의 제곱근이나 로그 계산이 있습니다. 복소수 영역에서는 음수의 제곱근을 구할 수 있지만, 기본 컴퓨터 연산 체계에는 이를 표현하는 메커니즘이 없습니다.

또한 소프트웨어가 정수 전용 저장 공간에 부동 소수점 연산을 수행하려고 하면 부적절한 연산 오류가 발생합니다. 이는 데이터에 수행하려는 연산(부동 소수점 연산)과 실제 저장된 데이터(정수) 간의 불일치 때문입니다.

2. 0으로 나누기 (Division by Zero)

정수를 0으로 나누려고 하면 부동 소수점 예외가 발생합니다. NaN(Not a Number)이나 무한대(infinity)로 나누려는 경우에도 마찬가지입니다. 대표적인 예로는 1/0, log(0) 같은 연산이 있습니다.

3. 오버플로우 (Overflow)

연산 결과가 해당 자료형이 표현할 수 있는 최댓값을 초과하면 오버플로우 예외가 발생합니다. 즉, 결과 값이 표현 가능한 상한보다 크거나 허용 범위를 벗어나는 경우입니다.

4. 언더플로우 (Underflow)

언더플로우는 계산 결과가 해당 데이터 타입이 담을 수 있는 최솟값보다 작아질 때 발생합니다.

5. 부정확함 (Inexact)

연산 결과가 예상 값과 다를 때 부정확(inexact) 예외가 발생합니다. 이는 무한한 정밀도와 지수 범위로 연산을 수행할 수 없는 현실적인 한계 때문에 생기는 현상입니다.

다행히 이런 상황 중 일부는 우아하게(gracefully) 처리할 수 있습니다. 예를 들어 프로그램이 0으로 나누기를 시도할 때, 프로그램이 그대로 충돌(crash)하도록 두는 것보다 오류 메시지를 반환하고 안전하게 종료하는 것이 바람직합니다. 다음은 C++의 try-catch 구문을 활용한 예외 처리 코드입니다.

#include <iostream>
#include <stdexcept>
using namespace std;

float Div(float num, float den)
{
    if (den == 0) {
        throw runtime_error("Math error: Attempted to divide by 0\n");
    }
    return (num / den);
}

int main()
{
    float num, denom, result;
    num = 10;
    denom = 0;
    try {
        result = Div(num, denom);
        cout << "The quotient is " << result << endl;
    }
    catch (runtime_error& e) {
        cout << "Exception occurred" << endl << e.what();
    }
}

이 코드에서 main 함수 내부의 try 블록이 Div 함수를 호출합니다. denom이 0이 아니면 Div 함수는 몫(quotient)을 반환하고, 0이라면 runtime_error 예외를 던집니다(throw). catch 블록은 이 예외를 가로채어 "Exception occurred"라는 문자열을 출력한 뒤, runtime_error 객체 e에 대해 what() 함수를 호출합니다.

여기서 what()은 예외를 식별하기 위해 사용되는 가상(virtual) 함수로, stdexcept 헤더 파일에 정의된 standard exception 클래스의 멤버입니다. 결과적으로 "Math error: Attempted to divide by 0"이라는 메시지가 화면에 출력됩니다.

실행 결과

C++ 부동 소수점 예외(Floating Point Exception) 완벽 가이드: 원인과 해결 방법

C++에서 부동 소수점 예외를 예방하는 방법

C++에서 부동 소수점 예외를 방지하려면 다음 사항을 반드시 확인해야 합니다.

  • 함수에 전달되는 모든 매개변수의 유효성 검사
  • 적절한 자료형과 포맷 사용
  • 나눗셈 연산 시 제수(divisor)가 0인지 명시적으로 검사
  • 큰 연산 결과가 필요한 경우 double 등 더 넓은 범위의 자료형으로 확장

마무리

C++의 부동 소수점 예외는 숫자 값에 대한 부적절한 연산으로 인해 발생하며, 프로그램의 정상적인 실행을 방해할 수 있습니다. 이러한 오류를 피하려면 함수에 전달되는 모든 매개변수를 꼼꼼히 점검하고 적절한 데이터 타입을 사용하는 것이 중요합니다. 나아가 try-catch 구문으로 부동 소수점 예외를 미리 잡아(catch) 처리하면 더욱 안정적인 프로그램을 만들 수 있습니다.