최초에 주어진 함수는 다음과 같다.
int32_t add2(int32_t x, int32_t y) {
if(x + y > INT32_MAX) {
throw std::invalid_argument("Overflow!!");
} else {
return (x + y);
}
}
이에 대한 개선된 함수는 다음과 같다고 한다. 그러나 여전히 문제가 있다.
int32_t add2(int32_t x, int32_t y) {
if(x + y > INT32_MAX || x + y < INT32_MIN) {
throw std::invalid_argument("Overflow!!");
} else {
return (x + y);
}
}
위 코드에서 문제가 되는 점
x + y가 int32_t
데이터 타입이 허용하는 범위를 벗어나면 이미 오버플로우가 발생한다. 예를 들어 x가 2,147,483,647, y가 1이면 x + y은 이미 오버플로우가 발생하여 -2,147,483,648이 되어있기 때문에 위의 로직으로는 걸러지지가 않는다.
따라서
- x, y의 합이 오버플로우가 발생하는 로직을 개선한 함수를 구현하고,
- 추가적으로 x, y의 차에 대한 함수도 구현하기.
1. 최초에 제출한 코드
#include <iostream>
#include <cstdint>
using namespace std;
int32_t add(int32_t, int32_t);
int32_t sub(int32_t, int32_t);
int main() {
int32_t x = -2000000000;
int32_t y = 2000000000;
try {
cout << sub(x, y) << endl;
} catch(invalid_argument& ia) {
cout << ia.what() << endl;
}
return 0;
}
int32_t add(int32_t x, int32_t y) {
if(x >= 0) {
if(y > (INT32_MAX - x)) {
throw invalid_argument("Overflow: 두 수의 합이 허용하는 범위를 넘었습니다.");
}
} else {
if(y < (INT32_MIN - x)) {
throw invalid_argument("Overflow: 두 수의 합이 허용하는 범위보다 작습니다.");
}
}
return x + y;
}
int32_t sub(int32_t x, int32_t y) {
if(x >= 0) {
if(y < (INT32_MAX - x)) {
throw invalid_argument("Overflow: 두 수의 합이 허용하는 범위를 넘었습니다.");
}
} else {
if(y > (INT32_MIN - x)) {
throw invalid_argument("Overflow: 두 수의 합이 허용하는 범위보다 작습니다.");
}
}
return x - y;
}
2. 그럼 위의 코드 실행은 정상적으로 동작하는가?
sub2 함수 실행시 문제가 있었음. 기본적으로 다음과 같은 값 갖고 테스트를 해봤을 때
x = 0, y = 2,147,483,646, 기대값 = -2,147,483,646
x = 0, y = 0, 기대값 = 0
x = 2, y = 0, 기대값 = 2
모든 경우에서 발생해서는 안되는 에러가 발생함.
x 값을 입력하세요 (허용범위: -2,147,483,648 ~ 2,147,483,647). 2
y 값을 입력하세요 (허용범위: -2,147,483,648 ~ 2,147,483,647). 0
입력받은 x값: 2
입력받은 y값: 0
x - y = Overflow: 두 수의 합이 허용하는 범위를 넘었습니다.
add2 함수를 구현한 후, 단순히 부호만 뒤집어주면 sub2 함수가 실행이 될 것이라고 생각을 했는데, 이 부분에 대해 아예 잘못 생각하고 테스트도 하지 않고 넘어가서 전혀 엉뚱하게 작동함. 따라서 새로 로직을 다음과 같이 구성함.
(1) 두 변수의 차를 구할 때 오버플로우가 발생할 가능성이 있는 경우
0도 양수로 포함
- x가 음수, y가 양수인 경우
- x가 양수, y가 음수인 경우
1번 경우에서 x - y를 하는 경우, 음수 오버플로우가 발생하여 연산 값이 양으로 나오게 되고, 2번 경우에서는 양수 오버플로우가 발생하여 음의 경우가 나오게 됩니다. 따라서 이 두 부분을 걸러내야 함.
(2) 위의 코드 자체의 문제점
쓸데없이 if문 안에 if문을 들여서 가독성을 떨어뜨리는 문제
두가지를 고려했을 때 수정한 코드는 다음과 같음.
int32_t sub2(int32_t x, int32_t y) {
// 두 변수의 차를 계산하여 담을 변수
int32_t subResult = x - y;
// x가 음수, y가 양수인 경우에 subResult가 양수인 경우
if(x < 0 && y > 0 && subResult > 0) {
throw invalid_argument("negative overflow");
}
// x가 양수, y가 음수인 경우에 subResult가 음수인 경우
else if(x > 0 && y < 0 && subResult < 0) {
throw invalid_argument("positive overflow");
}
return subResult;
}
한편, add2 함수 또한 가독성 측면에서 문제가 있어서 새로이 로직을 구성하기로 함.
0도 양수로 포함
- x가 양수인데 y도 양수인 경우 둘의 합이 음수이면 positive overflow
- x가 음수인데 y도 음수인 경우 둘의 합이 양수이면 negative overflow
int32_t add2(int32_t x, int32_t y) {
// 두 변수의 합을 계산하여 담을 변수
int32_t addResult = x + y;
// x 및 y가 양수인데 그 합이 음수인 경우 positive overflow 예외처리
if(x >= 0 && y >= 0 && addResult < 0) {
throw invalid_argument("positive overflow");
}
// x 및 y가 음수인데 그 합이 양수인 경우 negative overflow 예외처리
else if(x < 0 && y < 0 && addResult >= 0) {
throw invalid_argument("negative overflow");
}
return addResult;
}
3. 재구성한 코드에 대한 테스트
위에서 재구성한 코드 전체 내용은 다음과 같음.
#include <iostream>
#include <cstdint>
using namespace std;
int32_t add2(int32_t, int32_t);
int32_t sub2(int32_t, int32_t);
int main() {
// x, y의 입력값은 각각 정상범위 (-2,147,483,648 ~ 2,147,483,647)라고 가정.
int32_t x;
int32_t y;
cout << "x 값을 입력하세요 (허용범위: -2,147,483,648 ~ 2,147,483,647). ";
cin >> x;
cout << "y 값을 입력하세요 (허용범위: -2,147,483,648 ~ 2,147,483,647). ";
cin >> y;
try {
cout << "입력받은 x값: " << x << endl;
cout << "입력받은 y값: " << y << endl;
cout << "x + y = " << add2(x, y) << endl;
cout << "x - y = " << sub2(x, y) << endl;
} catch(invalid_argument& ia) {
cout << ia.what() << endl;
}
return 0;
}
int32_t add2(int32_t x, int32_t y) {
// 두 변수의 합을 계산하여 담을 변수
int32_t addResult = x + y;
// x 및 y가 양수인데 그 합이 음수인 경우 positive overflow 예외처리
if(x >= 0 && y >= 0 && addResult < 0) {
throw invalid_argument("positive overflow");
}
// x 및 y가 음수인데 그 합이 양수인 경우 negative overflow 예외처리
else if(x < 0 && y < 0 && addResult >= 0) {
throw invalid_argument("negative overflow");
}
return addResult;
}
int32_t sub2(int32_t x, int32_t y) {
// 두 변수의 차를 계산하여 담을 변수
int32_t subResult = x - y;
// x가 음수, y가 양수인 경우에 subResult가 양수인 경우
if(x < 0 && y >= 0 && subResult > 0) {
throw invalid_argument("negative overflow");
}
// x가 양수, y가 음수인 경우에 subResult가 음수인 경우
else if(x >= 0 && y < 0 && subResult < 0) {
throw invalid_argument("positive overflow");
}
return subResult;
}
다음 변수를 입력하여 각각 테스트를 진행한 결과
x |
y |
add2 연산 기대값 |
add2 실제 결과값 |
sub2 연산 기대값 |
sub2 실제 결과값 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
-1 |
-1 |
1 |
0 |
1 |
1 |
1 |
1 |
1 |
1 |
2 |
2 |
0 |
0 |
2,147,483,647 |
-2,147,483,648 |
-1 |
-1 |
positive overflow |
positive overflow |
-2,147,483,648 |
2,147,483,647 |
-1 |
-1 |
negative overflow |
negative overflow |
1 |
2,147,483,647 |
positive overflow |
positive overflow |
-2,147,483,646 |
-2,147,483,646 |
2,147,483,647 |
1 |
positive overflow |
positive overflow |
2,147,483,646 |
2,147,483,616 |
0 |
-2,147,483,648 |
-2,147,483,648 |
-2,147,483,648 |
positive overflow |
positive overflow |
-1 |
-2,147,483,648 |
negative overflow |
negative overflow |
2,147,483,647 |
2,147,483,647 |
2,147,483,647 |
2,147,483,647 |
positive overflow |
positive overflow |
0 |
0 |
-2,147,483,648 |
-2,147,483,648 |
negative overflow |
negative overflow |
0 |
0 |
4. 수행 중 배운 점
언더플로우와 오버플로우 개념의 혼동
기존에는 특정 데이터타입의 최대값보다 큰 값이 주어진 것을 오버플로우, 최소값보다 작은 값이 주어진 것을 언더플로우라고 착각하고 있었다.
- 오버플로우: 특정 데이터타입이 담을 수 있는 범위를 넘어선 값이 주어진 경우
- 언더플로우: 부동소수점 연산시에 precision 허용 범위보다 더 적은 값을 표현하지 못하는 경우
5. 이어서 수행할 과제
예외처리를 하지 않고 처리하는 방법을 적용하여 코드 개선하기