본문 바로가기

Kotlin

공식 문서로 배우는 Kotlin - 5. Basic Types - Unsigned integer types

다섯 번째, 부호 없는 정수 타입입니다.

 

정수 타입에 더하여, Kotlin은 부호 없는 정수를 위한 타입을 다음과 같이 제공합니다.

타입 사이즈(bits) 최솟값 최댓값
UByte 8 0 255
UShort 16 0 65,535
UInt 32 0 4,294,967,295 (2^32 - 1)
ULong 64 0 18,446,744,073,709,551,615 (2^64 - 1)

 

부호 없는 정수 타입들은 그에 대응하는 부호 있는 타입들이 제공하는 연산들 대부분을 지원합니다.

부호 없는 정수 타입은 해당 타입에 대응하는 같은 길이의 부호 있는 타입을 단일 저장 프로퍼티로 갖는 인라인 클래스로 구현됩니다. 부호 없는 타입과 부호 있는 타입 상호 간에 변환해야 한다면, (두 타입은 서로 다르므로) 모든 함수 호출과 연산이 (변환되는) 새로운 타입을 지원하도록 기존 코드를 갱신해야 합니다.

 

부호 없는 배열과 범위

주의
부호 없는 배열과 그에 대한 연산은 베타 단계입니다. 어느 때든지 이전과 호환되지 않게 변경될 수 있습니다. 그리고, 사용할 때는 동의(opt-in)가 필요합니다(관련된 내용은 밑에 나옵니다). 

참고
부호 없는 정수형 타입은 코틀린 1.3에서 베타로 추가됐고, 1.5 버전에서 배열과 그에 대한 연산을 제외하고 정식으로 추가됐습니다.

 

원시 타입과 같이, 각각의 부호 없는 타입들은 각각에 대응하는 다음과 같은 배열 타입이 있습니다.

  • UByteArray
  • UShortArray
  • UIntArray
  • ULongArray

부호 있는 정수 배열과 같이 (래퍼 클래스로 변경하는) 박싱 오버헤드 없는 API를 제공합니다.

 

부호 없는 배열을 사용하면, 기능이 아직 안정화되지 않았다는 경고를 받게 됩니다. 경고를 없애려면 @ExperimentalUnsignedTypes 어노테이션으로 사용 동의(opt-in)를 해야 합니다. 부호 없는 배열을 사용하는 부분이 있는 API를 만들어서 제공할 때, 사용하는 측에서 동의하게 할지, 안 할지는 자신이 결정할 문제이지만, 부호 없는 배열은 안정화된 기능이 아니기 때문에 언어 변경에 따라 API 사용 측에 문제가 생길 수 있다는 것은 기억하고 있어야 합니다. 관련된 좀 더 자세한 내용은 여기에서 확인할 수 있습니다.

 

UInt와 ULong 대해서 UIntRange, UIntProgression, ULongRange, ULongProgression 클래스로 범위와 진행 방향이 지원됩니다. 이 클래스들은 모두 정식 버전입니다.

 

부호 없는 정수 리터럴

부호 없는 정수를 보다 쉽게 사용할 수 있도록, Kotlin은 정수 리터럴에 끝에 붙여 부호 없음을 나타내는 접미어를 제공합니다.

  • u와 U 부호 없는 정수 리터럴을 위한 접미어입니다. 정확한 타입은 원하는 타입에 의해서 결정됩니다. 원하는 타입이 지정되지 않은 경우에 컴파일러는 리터럴의 크기에 따라 UInt나 ULong을 적절히 사용합니다.
val b: UByte = 1u  // UByte, 원하는 타입이 지정된 경우
val s: UShort = 1u // UShort, 원하는 타입이 지정된 경우
val l: ULong = 1u  // ULong, 원하는 타입이 지정된 경우

val a1 = 42u // UInt: 원하는 타입이 지정되지 않은 경우. 크기에 맞게 UInt
// 원하는 타입이 지정된 경우. 크기가 UInt를 넘어서므로 ULong
val a2 = 0xFFFF_FFFF_FFFFu
  • ul과 UL은 ULong으로 명시적으로 지정하는 접미어입니다.
// a에 대해 타입 지정을 안 했지만 UL에 의해서 ULong
val a = 1UL

 

사용 예

부호 없는 정수 타입의 주된 사용처는 양수를 표현할 때 그 값이 커서 정수의 모든 비트를 활용해야 할 때입니다. 예를 들어, 32비트 색상 값인 AARRGGBB 같이 부호 있는 정수형의 범위를 초과하는 16진수 상수를 표현하기 위해 사용합니다.

data class Color(val representation: UInt)

val yellow = Color(0xFFCC00CCu)

 

부호 없는 정수는 명시적으로 리터럴을 toByte()로 캐스트 하는 것 없이 바이트 배열을 초기화할 때 사용할 수 있습니다.

val byteOrderMarkUtf8 = ubyteArrayOf(0xEFu, 0xBBu, 0xBFu)

 

또 다른 사용 예는 네이티브 API와 상호 운용하는 경우입니다. Kotlin은 시그니처에 부호 없는 타입이 포함된 네이티브 선언을 허용합니다. 네이티브의 부호 없는 타입을 부호 있는 타입으로 변경하지 않고 그대로 유지함으로써 원래 의미를 변경 없이 그대로 유지합니다.

 

원래 의도와 다른 사용(Non-goals)

부호 없는 타입은 단지 양수와 0을 표현하기 위해 만들어졌습니다. 음수가 아닌 값만을 요구하는 애플리케이션 영역에서 사용하도록 만들어진 것이 아닙니다. 예를 들어, 컬렉션 크기(size)나 인덱스 값을 위해 사용하도록 만들어진 것이 아닙니다. 

 

이와 관련해서는 다음과 같은 두 가지 이유가 있습니다.

  • 부호 있는 정수 타입을 사용하면 의도치 않은 오버플로우나 빈 배열에서 List.lastIndex가 -1이 되는 거 같은 이상 징후를 검출할 수 있습니다. 반면에 부호 없는 타입을 사용하면 양수 범위가 훨씬 커져, 오버플로우가 발생해야 하는 상황에서 발생하지 않게 되며, -1은 수용할 수 없어 이상 징후에 대응할 수 없습니다.
  • 값의 범위가 부호 있는 타입의 서브셋이 아니기 때문에, 부호 없는 정수 타입은 부호 있는 타입의 범위 제한 형태(range-limit version)로 다룰 수 없습니다. 부호 있는 타입과 부호 없는 타입은 상호 간에 어느 쪽으로도 하위 타입이 아닙니다.

그러므로, 양수만 사용해야 하는 영역에서는 부호 없는 타입을 사용하지 않고, 부호 있는 타입을 사용하고 로직을 통해 예외 사항을 적절히 제어해야 합니다.