자바 예외처리(exception)에 대해 보다가 문득 ArithmeticException에 대해 궁금해져서 정리하게 된 글입니다.

개요
아래 코드의 실행 결과는 뭘까?
public class Main {
public static void main(String[] args) {
int a = 5, b = 0;
try {
System.out.print(a / b);
} catch (ArithmeticException e) {
System.out.print("출력1");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.print("출력2");
} catch (NumberFormatException e) {
System.out.print("출력3");
} catch (Exception e) {
System.out.print("출력4");
} finally {
System.out.print("출력5");
}
}
}
정답은 '출력1출력5' 이다.
첫번째 try문에서 a/b를 할 때, 0으로 나누기 때문에, `ArithmeticException`이 발생하기 때문이다.
개발자라면 한 번쯤 "0으로 나누기" 에러를 본 적이 있을 것이다. 자바(Java)에서는 `ArithmeticException`이 발생하는데, 자바스크립트(JavaScript)에서는 `Infinity`라는 값이 튀어나온다.

왜 이런 차이가 발생하는 걸까? 단순히 "언어마다 다르다"라고 외우는 것을 넘어, 컴퓨터가 수학적으로 불가능한 연산을 어떻게 처리하는지에 대한 '동작 원리'로 이해해보자.
1. 근본적인 문제: CPU는 0으로 나누는 것을 어떻게 처리할까?
모든 프로그래밍 언어는 결국 CPU(중앙 처리 장치)에서 기계어로 번역되어 실행된다. 우리가 5 / 0 코드를 작성하면, CPU는 이 연산을 수행해야 한다.
이때, CPU는 연산하려는 숫자의 타입에 따라 두 가지 다른 방식으로 동작한다.
가. 정수 (int, long) 연산: "불가능한 명령"
- CPU 동작: 정수 연산 장치(ALU)는 5 / 0 (정수 나누기)을 "수학적으로 정의되지 않아 실행 불가능한" 명령으로 인식한다.
- 결과: CPU는 즉시 하던 일을 멈추고, 운영체제(OS)에 "치명적인 오류가 발생했다!"는 하드웨어 인터럽트(트랩) 신호를 보낸다. (정확히는 Divide Error 신호)
나. 실수 (float, double) 연산: "특별한 값으로 처리"
- CPU 동작: 실수(부동소수점) 연산 장치(FPU)는 IEEE 754라는 국제 표준을 따른다. 이 표준에는 "무한대(Infinity)"와 "숫자가 아님(NaN, Not a Number)"이라는 특별한 값이 정의되어 있다.
- 결과: 하드웨어 오류가 발생하지 않는다. 대신 표준에 따라 5.0 / 0.0은 Infinity로, 0.0 / 0.0은 NaN으로 처리된 '유효한' 값을 반환한다.
2. 언어들의 3가지 선택: 안전성 vs 유연성 vs 성능
CPU가 이렇게 두 가지 방식으로 동작하기 때문에, 각 프로그래밍 언어는 이 신호(혹은 값)를 어떻게 처리할지 '설계 철학'에 따라 선택해야 했다.
철학 1: 안전성 우선 (Exceptions) - "개발자에게 오류를 명확히 알려준다"
"정수 0 나눗셈은 하드웨어 오류야. 이걸 무시하고 넘어가면 프로그램에 더 큰 버그가 생길 수 있어. 즉시 예외(Exception)를 발생시켜서 개발자가 이 문제를 인지하고 처리하도록 강제하자."
- 언어: Java, Python, C#
- 동작: CPU의 하드웨어 인터럽트 신호를 받아서, 각 언어의 문법에 맞는 예외 객체로 감싸서(Wrapping) 던진다.
- Java: ArithmeticException
- Python: ZeroDivisionError
- C#: DivideByZeroException
- 특징: 실수(float, double) 연산은 IEEE 754 표준을 따라 Infinity를 반환하므로, 정수와 실수의 동작이 다르다.
// Java
System.out.println(5 / 0); // 🛑 ArithmeticException 발생
System.out.println(5.0 / 0.0); // Infinity (예외 아님)
철학 2: 유연성 우선 (Special Values) - "어떻게든 계산을 이어나간다"
"프로그램이 멈추는(crashing) 것이 최악이다. 특히 웹 브라우저 같은 환경에서는 계산 하나 잘못했다고 전체 스크립트가 멈추면 안 된다. 모든 숫자를 실수처럼 취급해서 '무한대'라는 값으로라도 계산을 이어가자."
- 언어: JavaScript
- 동작: JavaScript는 Java와 달리 int나 long 같은 순수 정수 타입이 없다. 모든 숫자는 내부적으로 64비트 부동소수점(Java의 double과 유사)으로 처리된다.
- 특징: 5 / 0이든 5.0 / 0.0이든 모두 실수 연산으로 취급되어 IEEE 754 표준에 따라 Infinity를 반환한다. 예외가 발생하지 않는다.
// JavaScript
5 / 0 // Infinity (예외 아님)
-5 / 0 // -Infinity
0 / 0 // NaN
철학 3: 성능 우선 (Undefined Behavior) - "하드웨어에 모든 것을 맡긴다"
"언어 차원에서 이런 예외 처리를 추가하는 것은 모두 약간의 성능 저하를 유발한다. 우리는 1클럭이라도 빨라야 한다. 하드웨어의 오류 신호를 그대로 노출시키고, 그 책임은 프로그래머에게 맡긴다."
- 언어: C, C++
- 동작 (정수): 정수 0 나눗셈에 대한 하드웨어 오류를 언어 차원에서 막아주지 않는다. 이는 **"정의되지 않은 동작 (Undefined Behavior, UB)"**을 유발한다.
- 결과 (UB): 프로그램이 그 즉시 충돌(crash)할 수도, 이상한 쓰레기 값을 반환할 수도, 혹은 당장은 멀쩡해 보이다가 나중에 완전히 다른 곳에서 오작동할 수도 있다. 무슨 일이 일어날지 아무도 보장할 수 없어 가장 위험하다.
3. 한눈에 비교하기
| 언어 | 정수 / 0 (e.g., 5 / 0) | 실수 / 0.0 (e.g., 5.0 / 0.0) | 설계 철학 |
| Java | 🛑 ArithmeticException (예외) | Infinity (무한대 값) | 안전성 (타입별 분리) |
| Python | 🛑 ZeroDivisionError (예외) | 🛑 ZeroDivisionError (예외)¹ | 안전성 (매우 엄격) |
| C# | 🛑 DivideByZeroException (예외) | Infinity (무한대 값) | 안전성 (Java와 동일) |
| JavaScript | Infinity (무한대 값)² | Infinity (무한대 값) | 유연성 (모두 실수 취급) |
| C / C++ | 💥 Undefined Behavior (UB) | Infinity (표준에 따름) | 성능 (하드웨어 직결) |
¹ Python은 실수 연산조차 0으로 나누는 것을 허용하지 않고 예외를 발생시키는 매우 엄격한 정책을 사용한다.
² JavaScript는 int 타입이 없으므로 5 / 0 자체가 부동소수점 연산이 된다.
결론
"0으로 나누기"라는 단순해 보이는 연산 하나에도 이처럼 복잡한 하드웨어의 동작 원리와, 각 언어가 추구하는 안전성, 유연성, 성능이라는 핵심 가치가 담겨 있다.
내가 사용하는 언어가 왜 이런 선택을 했는지 이해한다면, 예외를 만나도 당황하지 않고 더욱 견고한 코드를 작성하는 데 도움이 될 것이다.
'Development' 카테고리의 다른 글
| 권한 관리, 언제까지 if-else로 땜빵할 거야? (RBAC부터 Zanzibar 찍먹까지) (1) | 2025.12.08 |
|---|---|
| [Cursor] Playwright로 테스트부터 디버깅까지 한번에 끝내기 (0) | 2025.11.11 |
| [React] 배포 후 간헐적으로 개발 서버에서 화면 에러가 뜨는 이유 (0) | 2025.10.14 |
| [Web] 브라우저 “탭( Tab )”을 노리는 공격, Tabnabbing (0) | 2025.10.13 |
| [C언어] return 0, 그리고 0과 1의 진짜 의미 (0) | 2025.10.06 |
