오버플로우(Overflow)와 언더플로우(Underflow)는 Java에서 자료형의 허용 범위를 벗어났을 때 발생하는 현상임.
핵심 개념: 자료형의 최대값에서 1을 더하면 최소값이 되고, 최소값에서 1을 빼면 최대값이 됨
이 현상이 발생하는 이유는 컴퓨터가 메모리에서 정해진 비트 수로만 숫자를 표현하기 때문임. 마치 자동차의 주행거리계가 999,999km에서 1km 더 달리면 000,000km로 돌아가는 것과 같은 원리임.
오버플로우는 정수 자료형에서 주로 발생함. Java의 각 정수 타입별 범위는 다음과 같음:
| 자료형 | 크기 | 최소값 | 최대값 |
|---|---|---|---|
| byte | 1바이트 | -128 | 127 |
| short | 2바이트 | -32,768 | 32,767 |
| int | 4바이트 | -2,147,483,648 | 2,147,483,647 |
| long | 8바이트 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 |
정수는 2의 보수법으로 저장되기 때문에, 최대값을 넘어서면 음수 영역으로 넘어감.
언더플로우는 두 가지 상황에서 발생함:
정수 타입에서 최소값보다 작은 값이 되는 경우
float나 double 타입에서 표현 가능한 가장 작은 양수보다 작은 값이 되어 0으로 변하는 경우
public class OverflowExample {
public static void main(String[] args) {
// int의 최대값 확인
int maxValue = Integer.MAX_VALUE;
System.out.println("int 최대값: " + maxValue); // 2147483647
// 오버플로우 발생
int overflow = maxValue + 1;
System.out.println("최대값 + 1: " + overflow); // -2147483648 (최소값으로 변함)
// 연속적인 오버플로우
System.out.println("최대값 + 2: " + (maxValue + 2)); // -2147483647
}
}
public class UnderflowExample {
public static void main(String[] args) {
// byte의 최소값 확인
byte minValue = Byte.MIN_VALUE;
System.out.println("byte 최소값: " + minValue); // -128
// 언더플로우 발생 (형변환 필요)
byte underflow = (byte)(minValue - 1);
System.out.println("최소값 - 1: " + underflow); // 127 (최대값으로 변함)
// 연속적인 언더플로우
System.out.println("최소값 - 2: " + (byte)(minValue - 2)); // 126
}
}
public class FloatUnderflowExample {
public static void main(String[] args) {
// float의 최소 양수값 근처
float verySmall = Float.MIN_VALUE; // 가장 작은 양수
System.out.println("Float 최소 양수: " + verySmall);
// 매우 작은 수 나누기 (언더플로우 가능성)
float result = verySmall / 2.0f;
System.out.println("최소값 / 2: " + result);
// 극도로 작은 값 생성
float extremelySmall = 1e-50f;
System.out.println("극소값: " + extremelySmall); // 0.0으로 표시될 수 있음
}
}
public class RealWorldExample {
public static void main(String[] args) {
// 큰 수의 곱셈에서 오버플로우 발생
int a = 2000000000; // 20억
int b = 2;
System.out.println("a: " + a);
System.out.println("b: " + b);
// 오버플로우 발생
int result = a * b;
System.out.println("a * b (int): " + result); // 음수가 나옴!
// 해결 방법: long 타입 사용
long safeResult = (long)a * b;
System.out.println("a * b (long): " + safeResult); // 정상적인 결과
}
}
// 잘못된 예: 무한루프 가능성
for (byte i = 0; i < 200; i++) {
// byte는 127까지만 가능하므로 127을 넘으면 -128이 되어 무한루프
}
// 올바른 예: 적절한 자료형 사용
for (int i = 0; i < 200; i++) {
// int 사용으로 안전함
}
public class SafeCalculation {
// 안전한 덧셈 메서드
public static int safeAdd(int a, int b) {
// 오버플로우 검사
if (a > 0 && b > 0 && a > Integer.MAX_VALUE - b) {
throw new ArithmeticException("Integer overflow");
}
if (a < 0 && b < 0 && a < Integer.MIN_VALUE - b) {
throw new ArithmeticException("Integer underflow");
}
return a + b;
}
}
큰 수 계산 시 적절한 자료형 선택
int 연산 결과가 int 범위를 벗어날 가능성이 있다면 long 사용BigInteger나 BigDecimal 사용Math 클래스의 안전한 메서드 활용
// Java 8부터 제공되는 안전한 연산 메서드
int result1 = Math.addExact(a, b); // 오버플로우 시 예외 발생
int result2 = Math.multiplyExact(a, b); // 오버플로우 시 예외 발생
범위 체크 습관화
// 입력값 범위 검증
public void processValue(int value) {
if (value < Integer.MIN_VALUE / 2 || value > Integer.MAX_VALUE / 2) {
System.out.println("위험한 범위의 값입니다.");
}
}
오버플로우와 언더플로우는 Java 프로그래밍에서 예상치 못한 버그를 만들 수 있는 중요한 개념임.
핵심 포인트:
Math 클래스의 메서드나 더 큰 자료형을 활용해야 함