다음과 같은 함수가 선언되어 있다.

이 함수 내부의 로직을 보면, 문제점이 하나 보인다.

if (x + y > INT32_MAX) 부분을 보면, 이 부분이 과연 정상적으로 실행될 수 있는 부분인지 먼저 확인해볼 필요가 있다.

예를 들어서 x의 값을 2000000000, y의 값도 마찬가지로 2000000000이라고 해보자. 이 둘을 합하면 어떤 결과가 나올지 다음 코드를 통해 확인해 보자.

#include <iostream>
#include <cstdint>

using namespace std;

int main() {
    int32_t x = 2000000000;
    int32_t y = 2000000000;

    cout << x + y << endl;
    return 0;
}

실행 결과:
-294967296

20억과 20억을 더했는데 전혀 엉뚱한 결과가 나왔다. 왜일까?

int32_t 데이터 타입에서 제공하는 값의 범위는 -2,147,483,648 ~ 2,147,483,647 이다. 여기서 최대값 + 1을 하게 되면 int32_t 타입의 변수 입장에서는 담을 수 있는 범위의 최대치를 넘겨버리게 된다. 이를 정수 오버플로우 (integer overflow)라고 한다.

'끝과 끝은 맞닿아 있다'라는 말처럼, 데이터 타입의 최소값과 최대값은 서로 맞닿아 있다. 그렇기 때문에 오버플로우가 발생한 경우 컴파일러는 증가분에 대한 값을 INT32_MAX의 다음 값인 최소 값으로 넘긴다. 따라서 -2,147,483,648을 리턴하게 된다.

만일 최대값 + 2를 하게 되면 위의 논리에 따라 -2,147,483,647을 리턴하게 된다.

이러한 문제는 역으로 최소값에 1을 빼려고 시도하는 경우에도 마찬가지로 나타난다.

#include <iostream>
#include <cstdint>

using namespace std;

int main() {
    int32_t x = INT32_MIN;
    int32_t y = -1;

    cout << x + y << endl;
    return 0;
}

위의 코드에서 출력되는 x + y의 값은 2,147,483,647가 된다. 최소값인 -2,147,483,648에서 1을 더 빼서 맞닿아 있는 최대값을 리턴하기 때문이다.

주의

이번 문제에 대해 찾아보던 중, 용어를 헷갈려 해서 다음과 같이 정리해 보고자 한다. 난 최대값의 범위를 넘어가서 계산하는 것을 오버플로우, 최소값보다 작은 값을 계산하는 것을 언더플로우라고 생각하고 있었는데 그동안에 용어에 대한 개념을 완전 잘못 알고 있었던 것이다. 두 용어를 다시 한번 더 정리하자면 다음과 같다.

  • 오버플로우: 특정 데이터 타입의 값 범위 밖의 연산이 일어나는 경우 (그게 최소값이던 최대값이던간에 관계없음)
  • 언더플로우: 나무위키에서 정리한 내용이 있어서 다음과 같이 인용해 본다 (https://namu.wiki/w/%EC%96%B8%EB%8D%94%ED%94%8C%EB%A1%9C)
    부동소수점 언더플로(floating point underflow) 또는 산술 언더플로(arithmetic underflow)는 컴퓨터에서 부동소수점으로 표현된 수에서, 지수(E)가 최솟값보다 낮아지는 상황을 뜻한다. 예를 들어 IEEE 754의 단정밀도 실수(single-precision)에서 표현가능한 지수의 값은 −126 ~ +127 이다. 그런데, 2-100 * 2-100 을 계산하면 2-200 이 되어야 하는데, 단정밀도에서는 지수의 값을 -126까지만 처리할 수 있으므로 범위를 벗어나는 값이 된다.

즉 언더플로우는 부동소수점의 정밀도와 관련된 개념으로, 표현이 가능한 지수 범위를 초과한 경우를 일컫는 말이다.

그렇다면 위의 함수를 개선하여 올바른 코드를 짜는 방법은?

그건 다음 시간에 이어서 올리겠다.

반응형

+ Recent posts