본문 바로가기

코틀린

코틀린: 진법 변환 구현

십진수 123을 8진수로 어떻게 변환할까요? toString(radix:Int) 함수를 사용하면 됩니다. radix는 진수입니다. 아주 쉽죠!

fun main() {
    val x: Int = 123
    println("$x is ${x.toString(8)} in octal representation")
}

라이브러리를 사용하지 않고 직접 구현해 볼까요. 

아래 코드에서 StringBuffer 대신 StringBuilder를 사용해도 됩니다. StringBuffer와 StringBuilder에 대해서는 쉽게 다가가는 최신 프로그래밍:코틀린 58~59쪽(1.11절)에 자세히 설명했습니다. 함수 decimalToOctal()은 Int 타입를 인자로 전달받아 String 타입을 반환합니다. 함수 decimalToOctal()에서 핵심은 sb.insert() 입니다. 간단히 구현 과정을 살펴볼까요.

fun decimalToOctal(number: Int): String {
    var tmp: Int = number
    val sb = StringBuffer()

    while (tmp > 0) {
        sb.insert(0, tmp % 8) // 맨 앞에 삽입
        tmp /= 8
    }
    return if (sb.isEmpty()) "0" else sb.toString()
}

fun main() {
    val decimal: Int = 123
    val octal = decimalToOctal(decimal)
    println("$decimal is $octal in octal representation.")
}

 

tmp=123이므로, while 루프의 조건식 tmp > 0을 만족하며, while 루프를 실행합니다.

%는 나머지(remainder)를 구하는 연산 기호입니다. 예를 들면, 7 % 2 는 1입니다(7 / 2 는 3입니다). % 연산은 피연산자의 타입이 Int일 때 적용합니다. while 루프에서 123 % 8 = 3 입니다.

sb.insert(int offset, int i)에서 offset은 삽입할 위치이며, 정수 i는 String 타입으로 변환되어 StringBuffer 타입 객체 sb에 추가됩니다. 타입 선언 방식이 코틀린과 다르죠. StringBuffer와 StringBuilder는 자바(java) 라이브러리이기 때문입니다. sb는 첫 번째 원소 "3"을 맨 앞에 추가하며, sb는 ["3"]이 됩니다. 

tmp  /= 8 (tmp = tmp / 8)에서 tmp = 15입니다. 변수 tmp는 Int 타입이므로 15.375가 아니라 소수점 이하를 버린 15가 할당됩니다. Int 타입은 소수점 이하 값을 반올림하지 않고 버립니다(truncate). 

여전히 tmp > 0 이므로 while 루프를 반복합니다. 15 % 8 = 7이며, sb는 두 번째 원소 "7"을 맨 앞에 추가합니다. sb는 ["7", "3"]이 됩니다. tmp /= 8 에서 tmp = 1입니다. tmp 타입이 Int이므로,  tmp/8 = 15/8은1.875가 아니라 1입니다.

tmp > 0 이므로 while 루프를 반복합니다. 1 % 8 = 1이며, sb는 세 번째 원소 "1"을 맨 앞에 추가합니다. sb는 ["1", "7", "3"]이 됩니다. tmp /= 8 에서 tmp = 0입니다. 더 이상 while 루프를 반복할 조건을 만족하지 못하므로, while 루프를 빠져나옵니다.

16진수 변환도 위 코드를 그대로 적용하면 될까요? 물론입니다. 다만 16진수에서는 10->A, 11->B, ..., 15->F 로 나타내기 때문에 나머지가 10 이상인 수는 문자로 변환하는 게 필요하겠죠! 

그리고, 보너스! 유니코드(unicode)의 한글 음절에서 자모를 분리하는 알고리즘도 진법 변환하는 방식과 꽤 유사합니다.  '한', '글'을 음절이라고 하며,  '한'의 자모 분리는 'ㅎ', 'ㅏ','ㄴ' 로 쪼개는 것을 말합니다. 참고하세요!