진수 변환, 왜 필요할까요?


숫자의 진법 변환은 컴퓨터 과학과 프로그래밍에서 매우 기본적인 동시에 중요한 개념입니다. 일반적으로 우리는 10진수를 사용하지만, 컴퓨터는 2진수를 기반으로 작동하며, 때로는 16진수나 8진수가 데이터를 표현하거나 디버깅하는 데 유용합니다. 더 나아가, 고유 ID 생성, 짧은 URL 구현, 데이터 압축 등 다양한 응용 분야에서 10진수 이상의 고유한 문자를 사용하는 고진수 변환이 필요할 때가 있습니다.

자바(Java)에서는 기본적으로 2진수, 8진수, 16진수 변환을 위한 메서드를 제공하지만, 36진수 (숫자 + 소문자 알파벳)를 넘어서는 62진수 (숫자 + 소문자 알파벳 + 대문자 알파벳)와 같은 고진수 변환은 직접 구현해야 합니다. 이번 글에서는 2진수부터 62진수까지 모든 양수 및 음수에 대해 범용적으로 동작하는 최적화된 진수 변환 유틸리티 클래스를 소개합니다.

NumberUtil: 2 ~ 62 진법 변환 유틸리티


다음은 주어진 long 타입 숫자를 원하는 진수로 변환하는 `numberToBase` 메서드를 포함하는 `NumberUtil` 클래스입니다. 이 메서드는 0부터 9까지의 숫자, 'a'부터 'z'까지의 소문자, 'A'부터 'Z'까지의 대문자를 활용하여 최대 62진수까지 표현할 수 있도록 설계되었습니다.

package com.example.util;

public final class NumberUtil {
 
    private static final char[] DIGITS = {
        '0','1','2','3','4','5','6','7','8','9',
        'a','b','c','d','e','f','g','h','i','j',
        'k','l','m','n','o','p','q','r','s','t',
        'u','v','w','x','y','z',
        'A','B','C','D','E','F','G','H','I','J',
        'K','L','M','N','O','P','Q','R','S','T',
        'U','V','W','X','Y','Z'
    };
 
    /**
     * 주어진 10진수(long)를 2에서 62 사이의 지정된 진수로 변환합니다.
     * 음수도 정확하게 처리하며, 변환된 결과는 String 형태로 반환됩니다.
     * 
     * @param num 변환할 10진수 숫자 (long 타입)
     * @param radix 목표 진수 (2 이상 62 이하)
     * @return 지정된 진수로 변환된 숫자의 문자열 표현
     * @throws IllegalArgumentException radix가 2 미만이거나 62 초과일 경우 발생
     */
    public static String numberToBase(long num, int radix) {
 
        if (radix < 2 || radix > 62) {
            throw new IllegalArgumentException("radix는 2 이상 62 이하만 가능합니다.");
        }
 
        if (num == 0L) {
            return "0";
        }
 
        boolean negative = (num < 0L);
        long value = negative ? -num : num; // 음수 처리
 
        // long 타입의 최대 자릿수는 64비트이므로, 2진수 기준 최대 64자리 + 부호(-)를 고려하여 버퍼 크기를 65로 설정합니다.
        char[] buffer = new char[65];
        int index = buffer.length; // 버퍼의 끝에서부터 채워나가기 위한 인덱스
 
        while (value != 0L) {
            // 현재 값을 radix로 나눈 나머지가 해당 진법의 한 자릿수가 됩니다.
            buffer[--index] = DIGITS[(int)(value % radix)];
            // 값을 radix로 나누어 다음 자릿수를 계산합니다.
            value /= radix;
        }
 
        if (negative) {
            buffer[--index] = '-'; // 음수인 경우 맨 앞에 '-' 추가
        }
 
        // 버퍼의 index 위치부터 (buffer.length - index) 길이만큼의 문자열을 생성하여 반환합니다.
        return new String(buffer, index, buffer.length - index);
    }

    // 간단한 테스트를 위한 main 메서드 (선택 사항)
    public static void main(String[] args) {
        System.out.println("10 (2진수): " + numberToBase(10, 2)); // 1010
        System.out.println("10 (16진수): " + numberToBase(10, 16)); // a
        System.out.println("61 (62진수): " + numberToBase(61, 62)); // Z
        System.out.println("1000 (36진수): " + numberToBase(1000, 36)); // rs
        System.out.println("-10 (2진수): " + numberToBase(-10, 2)); // -1010
        System.out.println("0 (10진수): " + numberToBase(0, 10)); // 0
        System.out.println("long.MAX_VALUE (62진수): " + numberToBase(Long.MAX_VALUE, 62));
        // 예상 출력: 1L9g41L7g61
    }
}


코드 설명:


  • DIGITS 배열: '0'부터 '9', 'a'부터 'z', 'A'부터 'Z'까지 총 62개의 문자를 포함하는 배열입니다. 이 배열의 인덱스가 각 진수의 숫자에 해당합니다. 예를 들어, DIGITS[10]은 'a'를 의미하며, 이는 10진수의 10을 표현합니다.

  • radix 유효성 검사: 진수는 최소 2 (2진수)부터 최대 62까지 가능하도록 제한합니다. 유효하지 않은 값이 입력되면 `IllegalArgumentException`을 발생시킵니다.

  • 0 처리: 입력 `num`이 0일 경우, 즉시 "0"을 반환하여 불필요한 계산을 방지합니다.

  • 음수 처리: 입력 `num`이 음수일 경우, `negative` 플래그를 true로 설정하고 `value`를 양수로 변환하여 계산합니다. 최종 결과에 '-' 부호를 추가하는 방식으로 처리합니다.

  • 변환 로직:

    1. `char[] buffer`는 변환된 진수 문자열을 저장할 임시 공간입니다. `long` 타입의 최대 자릿수를 고려하여 충분한 크기(65)로 할당합니다.

    2. `while (value != 0L)` 루프는 `value`가 0이 될 때까지 반복됩니다.

    3. `value % radix` 연산을 통해 현재 진법의 가장 낮은 자릿수(나머지)를 얻습니다. 이 값은 `DIGITS` 배열의 인덱스로 사용되어 해당하는 문자를 찾습니다.

    4. `buffer[--index]`를 통해 `buffer`의 끝에서부터 문자를 채워나갑니다. 이 방법은 변환된 숫자가 역순으로 계산되기 때문에 나중에 문자열을 생성할 때 순서를 뒤집을 필요 없이 바로 사용할 수 있게 합니다.

    5. `value /= radix` 연산을 통해 다음 자릿수를 계산하기 위해 `value`를 `radix`로 나눕니다.

  • 결과 반환: 모든 계산이 끝난 후, `index`부터 `buffer.length - index` 길이만큼의 문자 배열을 이용하여 새로운 `String` 객체를 생성하여 반환합니다. 이 방식으로 효율적으로 문자열을 구성합니다.


활용 예시:


이 `NumberUtil` 클래스는 고유 ID 생성 시스템, URL 단축 서비스, 데이터 인코딩/디코딩 등 다양한 곳에서 유용하게 활용될 수 있습니다. 특히 62진수는 숫자, 소문자, 대문자를 모두 사용하여 같은 길이의 문자열로 더 많은 정보를 표현할 수 있어 매우 효율적입니다.