본문 바로가기

Kotlin

Kotlin 도큐먼트: Numbers 데이터 타입

이 문서의 내용

    더보기

    Kotlin의 특징은 모든 데이터 타입에서 멤버 변수와 프로퍼티를 사용 할 수 있다는 점입니다.

    또한 어떤 데이터 타입들은 일반적인 클래스처럼 사용하지만 내부적으로 Primitive 타입(numbers, characters, boolean)과 같이 최적화됩니다.

    Integer 타입

    Kotlin에서 내장되어 있는 정수형 타입입니다.

    데이터 타입 데이터 크기(bits) 최소 값 최대 값
    Byte 8 -128 127
    Short 16 -32768 32767
    Int 32 -2,147,483,648 (-2^31) 2,147,483,647 (2^31 - 1)
    Long 64 -9,223,372,036,854,775,808 (-2^63) 9,223,372,036,854,775,807 (2^63 - 1)

    명시적으로 데이터 타입을 지정하지 않은 변수가 선언되면 컴파일러는 입력된 값에 따라서 사용 할 수 있는 가장 작은 데이터 크기의 타입을 추론합니다.

    이 때 자동으로 선택되는 데이터 타입의 최소 크기는 Int(64bits)입니다.

    val a = 1
    val b = 3000000000
    val c = 1L
    val d: Byte = 1
    코드 비고
    Line 1 a = 1 할당 할 수 있는 가장 작은 데이터 크기의 Int 타입이 추론됩니다.
    Line 2 b = 3000000000 할당 할 수 있는 가장 작은 데이터 크기의 Long 타입이 추론됩니다.
    Line 3 c = 1L 1L에 의해서 암묵적으로 Long 타입을 지정합니다.
    Line 4 d: Byte = 1 Byte 타입을 명시하고 있습니다.

    부동 소수점(Floating-point) 타입

    Kotlin은 실수 표현을 위해 FloatDouble 타입을 지원합니다. 이 타입들은 IEEE 754 표준을 준수합니다.

    데이터 타입 데이터 크기(bits) 부호(bits) 가수(bits) 지수(bits)
    Float 32 1 23 8
    Double 64 1 52 11

    Kotlin 컴파일러는 마침표(.)를 사용해 실수를 표현하는 데이터를 Double 타입으로 추론합니다.

    Float 타입을 명시하지 않고 실수를 저장하려면 f 또는 F 키워드를 사용합니다. Float 타입은 소수부 7자리까지 표현하며 그 이상은 반올림됩니다.

    val a = 3.14
    val b = 3.14f
    val c: Float = 3.14
    코드 비고
    Line 1 a = 3.14 실수는 항상 Double 타입으로 추론됩니다.
    Line 2 b = 3.14f f 키워드에 의해 암묵적으로 Float 타입을 지정합니다.
    Line 3 c: Float = 3.14 Float 타입을 명시하고 있습니다.
    더보기

    Kotlin과 다른 언어와의 차이점은 숫자를 표현하는 데이터 타입의 암묵적인 캐스팅을 방지하는 점입니다.

    fun main() {
        val a: Double = 123.456
        val b: Float = 123.456f
        val c: Int = 123
        val d: Short = 456
    
        funDouble(a)
        funDouble(b)
        funInteger(c)
        funInteger(d)
    }
    
    fun funDouble(value: Double) { }
    fun funInteger(value: Int) { }
    코드 비고
    Line 7 funDouble(a) Double 타입 인자를 받는 함수에 Double 타입을 전달합니다.
    Line 8 funDouble(b) Double 타입 인자를 받는 함수에 Float 타입을 전달합니다.
    Kotlin은 숫자 타입의 암묵적인 캐스팅을 지원하지 않으므로 컴파일 오류가 발생합니다.
    Line 9 funInteger(c) Int 타입 인자를 받는 함수에 Int 타입을 전달합니다.
    Line 10 funInteger(d) Int 타입 인자를 받는 함수에 Short 타입을 전달합니다.
    Kotlin은 숫자 타입의 암묵적인 캐스팅을 지원하지 않으므로 컴파일 오류가 발생합니다.

    JVM에서의 숫자 표현

    Kotlin의 숫자는 JVM에서 int doublePrimitive 타입으로 저장됩니다.

    Int?와 같은 Nullable 표현식을 사용하거나 Generic으로 지정된 경우 예외입니다. 이러한 Non-Primitive 숫자는 Java 클래스 Integer Double처럼 박싱(Boxing)됩니다.

    따라서 서로 다른 Nullable 객체가 동일한 숫자를 참조하더라도 실제로는 서로 다른 객체가 생성된다는 점을 주의해야합니다.

    val a: Int = 10000
    val boxedA: Int? = a
    val anotherBoxedA: Int? = a
    
    println(boxedA === anotherBoxedA)
    코드 비고
    Line 1 : Int Int 타입을 명시적으로 지정합니다.
    Line 2:3 : Int? Nullable 표현식 ?으로 인해 Integer 타입으로 박싱됩니다.
    Line 5 boxedA == anotherBoxedA boxedA는 박싱된 객체입니다.
    anotherBoxedA 또한 박싱된 객체로 boxedA와는 다른 시간에 인스턴싱된 객체입니다.
    따라서 비교 결과는 false 서로 다른 객체입니다.

    JVM은 -128부터 127까지의 숫자에 대해 메모리 최적화를 진행합니다.

    위의 예시와 동일하지만 이번에는 참조되는 숫자를 100으로 입력합니다. 출력 결과는 true로 JVM 메모리 최적화로 인해 동일한 값을 참조하는 것을 알 수 있습니다.

    val b: Int = 100
    val boxedB: Int? = b
    val anotherBoxedB: Int? = b
    
    println(boxedB === anotherBoxedB)
    더보기

    Kotlin에서 == 연산자는 값을 비교합니다. === 연산자는 객체의 주소를 비교합니다.

    예시의 boxedBanotherBoxedB는 서로 다른 객체이지만 여전히 == 연산자는 true로 유효합니다.

     val b: Int = 10000
    val boxedB: Int? = b
    val anotherBoxedB: Int? = b
    println(boxedB == anotherBoxedB)

    명시적인 숫자 타입 캐스팅

    Kotlin에서는 숫자 타입 간에는 암묵적인 타입 캐스팅이 발생하지 않습니다.

    따라서 아래와 같은 코드는 Kotlin에서 컴파일하지 못하고 Type mismatch 오류가 발생합니다.

    var a: Byte = 1
    var b: Int = a

    대신 명시적으로 숫자 타입을 캐스팅 할 수 있습니다.

    var a: Byte = 1
    var b: Int = a.toInt()
    코드 비고
    Line 2 a.toInt() Byte 타입을 Int 타입으로 캐스팅합니다.

    사실 대부분의 경우 문맥 상 데이터 타입이 추론되기 때문에 명시적인 캐스팅은 자주 쓰이지 않습니다.

    val a = 1L + 3
    
    // long type
    println(a.javaClass.name)

    숫자 타입에 대한 연산자

    Kotlin은 산술 연산에 대한 표준 집합 + - * / %을 지원합니다.

    더보기

    산술 연산은 각 숫자 타입의 클래스에서 멤버 변수로 선언되어 있습니다. 따라서 C++와 같이 Operator overloading을 통해 연산자의 동작을 재정의 할 수 있습니다.

    정수에 대한 / 연산은 항상 정수 결과를 만듭니다.

    val x = 5 / 2
    // println(x == 2.5)
    println(x == 2)
    코드 비고
    Line 1 x = 5 / 2 정수에 대한 division 결과로 Int 타입이 추론됩니다.
    Line 2 x == 2.5 정수 타입에는 == 연산자로 실수와 비교 할 수 없습니다.

    마찬가지로 다음 예시에서는 Long 타입이 추론되므로 == 연산자로 Long 타입과 비교해야 합니다.

    val x = 5L / 2
    // println(x == 2.5)
    // println(x == 2)
    println(x == 2L)

    부동 소수점 타입으로 추론하기 위해서는 toDouble() 또는 2.0처럼 실수 연산임을 명시합니다.

    val x = 5L / 2.toDouble()
    println(x == 2.5)

    정리 및 복습

    • 정수형 타입 Byte Short Int Long을 지원합니다. 부동 소수점 타입은 Float Double을 지원합니다.
    • Kotlin의 숫자는 JVM에서 Primitive 타입으로 최적화됩니다.
    • 반면 Nullable 타입은 Non-Primitive 타입으로 박싱(Boxing) 됩니다.
    • Kotlin의 숫자 타입 간에는 암묵적인 캐스팅이 발생하지 않습니다. 이와 같은 경우에는 toInt()처럼 명시적으로 캐스팅이 필요합니다.