java 고통의 NaN Infinity

Java에서 / 또는 % 연산자를 사용할 때, 제수가 정수 0인 경우 RuntimeException 중 하나인 ArithmeticException 발생한다.

1
2
3
4
5
6
7
8
9
10
int x = 12;
int y = 0;

try{
int z = x / y;
int z = x % y;
System.out.println("z:" + z);
}catch(ArithmeticException e){
e.printStackTrace(); // 다음과 같은 예외 발생: java.lang.ArithmeticException: / by zero
}

문제는 / 또는 % 연산자를 사용할 때, 제수가 정수 0이 아닌 실수 0.0 또는 0.0f인 경우이다. 이때는 ArithmeticException이 발생하는 것이 아니라, Infinity(무한), **NaN(Not a Number)**라는 값이 발생한다.

1
2
3
4
5
6
7
8
double a = 3 / 0.0;
double b = 5 % 0.0;

System.out.println(a); // 결과 : Infinity
System.out.println(b); // 결과 : NaN

System.out.println(a + 12); // 결과 : Infinity
System.out.println(b * 6); // 결과 : NaN

수학적으로 오류가 발생해야 하는 것임에도 ArithmeticException가 발생하지 않는다. 거기다 a, b변수는 double의 자료형임에도 Infinity, NaN 이라는 문자열 형태 데이터가 출력된다.(잘은 모르겠으나, Double wrapper 클래스가 개입하는 것이라고 개인적으로 추측해본다.) 거기다 이 Infinity, NaN는 오라클의 null 값과 비슷 한 특성을 가지고 있다. 오라클의 null에 어떤 연산을 가하면 미지의 쓰레기 값이 발생하는데, 이 java에서의 Infinity, NaN에 어떤 추가적 산술연산을 할경우 Infinity는 계속 Infinity가 되며 NaN도 계속 NaN이 된다.

Double.isInfinite(a)와 Double.isNaN(b)를 이용하여 Infinity와 NaN를 값을 확인 할 수 있다. 따라서 다음과 같이 Infinity, NaN을 방어하는 코드를 만들수 있다.

1
2
3
if(Double.isInfinite(a) || Double.isNaN(a)){
throw new ArithmeticException();
}

하지만 애시당초 0.0 또는 0.0f와 같은 값들을 정수 0로 치환하여 / 또는 % 연산시 자동으로 ArithmeticException가 발생하게 하는 편이 더 편할 것 같다.

마지막으로 Infinity, NaN에 관련하여 생각해야할 문제점이 있다. 그것은 Double.valueOf(x)의 멍청함이다.

1
2
3
4
5
6
7
8
9
10
11
double val = Double.valueOf("NaN");    // 오류가 나지 않고 NaN값이 저장된다.
double val2 = Double.valueOf("Infinity"); // 오류가 나지 않고 Infinity값이 저장된다.

double foo1 = 1000.0;
double foo2 = 1000.0;

foo1 += val;
foo2 += val2;

System.out.println(foo1); // 결과 NaN
System.out.println(foo2); // 결과 Infinity

위 예제를 보면 Double.valueOf의 인자로 문자열 NaN, Infinity를 받는다. 하지만 코드를 실행시 어떠한 오류없이 동작하며 콘솔에는 NaN, Infinity가 출력된다. 재앙이 아닐 수 없다.

정리하자면 java의 산술식에서 0.0또는 0.0f같은 값은 정수 0으로 치환하는 편이 좋아보이며, Double.valueOf함수를 사용시 반드시 Double.isInfinite, Double.isNaN를 통하여 후처리를 해줘야 할 것 같다.