진수 변환 함수 (62진수 까지)
진수 변환, 왜 필요할까요?
숫자의 진법 변환은 컴퓨터 과학과 프로그래밍에서 매우 기본적인 동시에 중요한 개념입니다. 일반적으로 우리는 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`를 양수로 변환하여 계산합니다. 최종 결과에 '-' 부호를 추가하는 방식으로 처리합니다.
변환 로직:
`char[] buffer`는 변환된 진수 문자열을 저장할 임시 공간입니다. `long` 타입의 최대 자릿수를 고려하여 충분한 크기(65)로 할당합니다.
`while (value != 0L)` 루프는 `value`가 0이 될 때까지 반복됩니다.
`value % radix` 연산을 통해 현재 진법의 가장 낮은 자릿수(나머지)를 얻습니다. 이 값은 `DIGITS` 배열의 인덱스로 사용되어 해당하는 문자를 찾습니다.
`buffer[--index]`를 통해 `buffer`의 끝에서부터 문자를 채워나갑니다. 이 방법은 변환된 숫자가 역순으로 계산되기 때문에 나중에 문자열을 생성할 때 순서를 뒤집을 필요 없이 바로 사용할 수 있게 합니다.
`value /= radix` 연산을 통해 다음 자릿수를 계산하기 위해 `value`를 `radix`로 나눕니다.
결과 반환: 모든 계산이 끝난 후, `index`부터 `buffer.length - index` 길이만큼의 문자 배열을 이용하여 새로운 `String` 객체를 생성하여 반환합니다. 이 방식으로 효율적으로 문자열을 구성합니다.
활용 예시:
이 `NumberUtil` 클래스는 고유 ID 생성 시스템, URL 단축 서비스, 데이터 인코딩/디코딩 등 다양한 곳에서 유용하게 활용될 수 있습니다. 특히 62진수는 숫자, 소문자, 대문자를 모두 사용하여 같은 길이의 문자열로 더 많은 정보를 표현할 수 있어 매우 효율적입니다.