본문 바로가기
Develop Study/Java 2025. 5. 7.

(25.05.07) NumberFormatException 와 오버플로우

 

알고리즘 코드 연습을 하는 과정에서 더블 체크를 했음에도, NumberFormatException이 발생해

타입변환이 이뤄지지 않아 당황을 했다.

 

오버플로우가 발생했다면, Java가 자동으로 처리해서 최종 값이 정확하지 않겠지만, 예외가 발생하지는 않는 것으로 알고 있었기 때문이다.

 

그 과정을 찾아보면서 좀더 관련 개념에 대해서 정리를 하고자 했다.



Q. 성냥개비는 숫자를 나타내기에 아주 이상적인 도구이다. 보통 십진수를 성냥개비로 표현하는 방법은 다음과 같다.

성냥개비의 개수가 주어졌을 때, 성냥개비를 모두 사용해서 만들 수 있는 가장 작은 수와 큰 수를 찾는 프로그램을 작성하시오.

입력
첫째 줄에 테스트 케이스의 개수가 주어진다. 테스트 케이스는 최대 100개 이다. 각 테스트 케이스는 한 줄로 이루어져 있고, 성냥개비의 개수 n이 주어진다. (2 ≤ n ≤ 100)

출력
각 테스트 케이스에 대해서 입력으로 주어진 성냥개비를 모두 사용해서 만들 수 있는 가장 작은 수와 가장 큰 수를 출력한다. 두 숫자는 모두 양수이어야 하고, 숫자는 0으로 시작할 수 없다.
public class Main {

    public static void main(String args[]) throws Exception {

        // INPUT SETTING
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        int n = Integer.parseInt(reader.readLine());

        // 주어진 성냥으로 만들 수 있는 최소숫자
        String[] match = new String[8];
        match[2] = "1";
        match[3] = "7";
        match[4] = "4";
        match[5] = "2";
        match[6] = "0";
        match[7] = "8";

        // 만들수 있는 최솟값 DP
        // DP[성냥수] = 만들 수 있는 최소 수(String)
        // 못만들 경우 null
        String[] minDP = new String[101];

        // DP 초기화
        for (int i = 2; i <= 7; i++) {
            minDP[i] = match[i];
        }
        // 처음은 따로 0이 맨앞에 있으면 안되기 때문에
        minDP[6] = "6";

        // minDP update
        for (int i = 8; i <= 100; i++) {
            // 성냥으로 만들 수 있는 최솟값들을 추가
            for (int j = 2; j <= 7; j++) {
                // 이전값이 없을 경우 갱신 안함
                if (minDP[i - j] != null) {

                    String updateNum = minDP[i - j] + match[j];

                    // minDP[i] 갱신
                    if (minDP[i] == null) {
                        minDP[i] = updateNum;
                    } else {
                        long minNum = Math.min(Long.parseLong(updateNum), Long.parseLong(minDP[i]));
                        minDP[i] = String.valueOf(minNum);
                    }

                }
            }
        }


        // 테스트 마다 최대 최솟값 찾기
        for (int test = 0; test < n; test++) {
            int num = Integer.parseInt(reader.readLine());

            // <최솟값 찾기>
            String min = minDP[num];

            // <최댓값 찾기>
            String max = findMaximum(num);

            System.out.println(min + " " + max);
        }
    }

    static String findMaximum(int target) {
        // 뒤를 최대한 채워야함
        // 가장 작은 1(2개로 만듬) 최대한 뒤를 채워야함
        // 단, 홀수 일 경우 맨 앞을 3개로 -> 7을 만들어야함

        int remainder = target % 2;

        // 2 이상의 짝수일 경우 & 홀수 일경우
        String result = "7";
        if (remainder == 0) {
            result = "1";
        }

        for (int i = 1; i < (target / 2); i++) {
            result = result + "1";
        }

        return result;
    }
}
  • 어떤 알고리즘을 통해서 풀어가는 정답이 있는 것이 아닌 특이한 디지털 사인, 또는 성냥 특징
    • Greedy의 중점이 “큰 숫자를 만든다” 가 아니라 “가능한 많은 숫자를 만든다” 로 바뀌어야함 = 성냥을 가장 적게쓰는 1을 가장 많이 만들어야함
  • 가장 작은 숫자 만들기 : DP 활용
    • 가장 앞에 올 수 있는 숫자들을 초기값들로 설정
    • DP를 통해 이전 값이 있을 경우 → 성냥을 추가하는 방식

→ 작은 숫자를 만들기 위해서도 성냥이 많이 소모되는 숫자인 8을 먼저 사용할 수 없음 18을 만들 수 있는데, 81이 만들어지는 경우가 있기 때문 : 따라서, 차라리 모든 경우의 수를 구하고 그중에 고르기 = DP

  • 가장 큰 숫자 만들기 : Greedy 활용
    • 가장 긴 숫자 = 가장 많은 숫자를 만드는 것이 목적
    • 따라서, 짝수 일경우, 2(1을 만들 수 있는 성냥 or LED)로 나눈 몫 만큼 1을 늘리면 됨
    • 홀수일 경우, 맨앞에 1개를 더 주면서 3개로 만들 수 있는 최대 숫자인 7이 맨앞으로

→ 큰 숫자를 만들기 위해 큰 숫자 먼저 맨 앞에서 만다는 방향은 잘못됨


NumberFormatException 발생 이슈

코드 상황

  • 자료형이 모두 String ↔ int 방식을 다루는 로직
  • 만약 Overflow로 String에서 int변환시 범위를 초과 할 경우, 발생했다면, MAX_VALUE 또는 MIN_VALUE로 반환되어 계산(순환되어)이 될 텐데 NumberFormatException 이 발생을 해버렸음 → 원인이 무엇일까

NumberFormatException

  • 이는 숫자가 아닌 포멧을 숫자로 바꾸려고 할 때 발생하는 런타임 예외
  • ex) ‘a’ 같은 char 형태는 integer 로 캐스팅을 시킬 수 있지만, ‘a’ 를 그대로 숫자로 바꿀 수는 없기 때문에 위의 예외가 발생할 수 있음

Integer.parseInt 에서의 Overflow

  • Java에서는 그냥 숫자로 계산이 아니라, Integer.parseInt 같이 parser 를 통해서 변환을 시도할 경우에는 NumberFormatException 을 던저버림
  • → 그렇다면 String 타입의 updateNum, minDP 값들은 전부 숫자로만 이뤄진 문자열인데 왜 parser가 변환을 못할까?
  • 파싱 중 Overflow
    • Integer.parseInt("999999999999") 처럼 ****숫자 자체가 오버플로우를 발생할수 있는 경우, 자동으로 처리하지않고 예외를 발생
int x = Integer.MAX_VALUE;  // 2147483647
x = x + 1;
System.out.println(x);      // -2147483648
  • 위의 경우는 “무음 오버플로우” 즉, 오버플로우가 연산 과정에서 발생을 했지만, 어떠한 예외 없이 순환된 값으로 계산을 해버린 것

해결방안

  • Integer의 Parser 가 아닌 Long의 Parser을 사용

참고

https://docs.oracle.com/en/java/javase/23/docs//api/java.base/java/lang/Integer.html#parseInt(java.lang.String)

 

Integer (Java SE 23 & JDK 23)

remainderUnsigned

docs.oracle.com

 


굉장히 단순한 예외이지만, 숫자로만 이뤄진 String을 Integer 로 파싱을 Java가 못하나 라고 처음에 생각했다.
하지만,  애초에 타입이 아닌 값 자체에 문제로 오버플로우로 인한 것이기 때문에,

Java의 자동 처리기능을 너무 과하게 맹신하고 로직을 짜는일 없이, 사소한 실수로 코드 작성에 걸림돌이 되지 않도록 해야할 것이다.