Skip to content

数値

Kotlinの数値型は以下を表現します:

数値型は、算術演算、カウンタ、測定、その他の計算などの数値データの格納と処理に使用します。

数値型の選択

ほとんどの場合、タスクに適した数値型を決定するために、以下のルールを参考にできます:

  • 整数には Int を使用する。
  • Int の範囲を超える整数には Long を使用する。
  • 小数には Double を使用する。
  • 低い精度が許容される場合や必要な場合には Float を使用する。
  • APIやデータ形式で要求される場合には ByteShort を使用する。

Kotlinでは、ベータ機能として も提供されています。

整数型

Kotlinは、サイズと値の範囲が異なる4つの整数型を提供しています:

サイズ (ビット)最小値最大値
Byte8-128127
Short16-3276832767
Int32-2,147,483,648 (-231)2,147,483,647 (231 - 1)
Long64-9,223,372,036,854,775,808 (-263)9,223,372,036,854,775,807 (263 - 1)

整数値の宣言

Kotlinは整数値に対して以下のリテラル形式をサポートしています:

  • 10進数: 123
  • 16進数: 0x0F
  • 2進数: 0b00001011

Kotlinでは、8進数のリテラルはサポートされていません。

数値変数を宣言するには、型を明示的に指定します:

kotlin
val one: Int = 1

// 読みやすさを向上させるためにアンダースコアを使用できます
val oneBillion: Long = 1_000_000_000
val hexBytes: Int = 0xFF_EC_DE_5E
val bytes: Int = 0b11010010_01101001_10010100_10010010

val oneByte: Byte = 1
val oneShort: Short = 1

Long 値を宣言するために、サフィックス L を付けることもできます:

kotlin
val oneLong = 1L

数値型を明示的に宣言すると、コンパイラは値がその型の範囲内に収まっているかチェックします:

kotlin
// 値がByteに収まる場合
val oneByte: Byte = 1

// エラー:値がByteに収まらない場合
val tooBig: Byte = 128

数値型を指定しない場合、Kotlinは値が Int の範囲に収まれば Int と推論します。それ以外の場合は Long と推論します:

kotlin
val million = 1_000_000 // Int
val threeBillion = 3_000_000_000 // Long

値が存在しない可能性がある場合は、Null 許容型を使用します:

kotlin
val maybeAbsent: Int? = null

浮動小数点型

小数部分を持つ数値のために、Kotlinは FloatDouble を提供しています。

浮動小数点型は IEEE 754 標準に従います。 Float は「単精度(single precision)」を、Double は「倍精度(double precision)」を反映しています。

浮動小数点型はサイズと精度が異なります:

サイズ (ビット)有効桁(ビット)指数(ビット)10進数での桁数
Float322486-7
Double64531115-16

浮動小数点数値の宣言

浮動小数点リテラルを宣言するには、小数点 (.) を含めるか、指数表記を使用します:

kotlin
val pi = 3.14
val avogadro = 6.02214076e23

デフォルトでは、Kotlinは浮動小数点リテラルを Double として推論します。 Float を宣言するには、サフィックス f または F を付けます:

kotlin
val pi = 3.14 // Double         
val eFloat = 2.7182818284f // Float

Float が保持できる以上の精度を持つ Float リテラルは、Kotlinによって丸められます。

値が存在しない可能性がある場合は、Null 許容型を使用します:

kotlin
val maybeAbsent: Double? = null

算術演算

Kotlinは数値に対する標準的な算術演算 +-*/% をサポートしています。

これらの演算子を使用して一般的な計算を行います:

kotlin
fun main() {
    println(1 + 2) // 3
    println(2_500_000_000L - 1L) // 2499999999
    println(3.14 * 2.71) // 8.5094
    println(10.0 / 3) // 3.3333333333333335
}

結果の型はオペランドの型に依存します。詳細は で確認してください。

カスタム数値クラスでこれらの演算子をオーバーライドできます。 詳細は 演算子のオーバーロード を参照してください。

整数の除算

整数値同士の除算は常に整数の結果を返します。小数部分は切り捨てられます:

kotlin
fun main() {
    val intValue = 5 / 2
    println(intValue) // 2
    
    val longValue = 5L / 2
    println(longValue) // 2
}

浮動小数点の値を返すには、少なくとも1つのオペランドを Float または Double にしてください:

kotlin
fun main() {
    val a = 5 / 2.0
    println(a) // 2.5
    
    val b = 5 / 2.toDouble()
    println(b) // 2.5
}

型変換

数値型は互いのサブタイプではありません。Kotlinは、暗黙的なデータ損失や予期しない動作を避けるために、明示的な変換を必要とします。

例えば、Double を期待する関数は、変換なしに IntFloat の値を受け取ることはできません:

kotlin
fun main() {
    fun printDouble(x: Double) { 
        print(x) 
    }

    val x = 1.0
    val xInt = 1    
    val xFloat = 1.0f
    val one: Double = 1 // エラー:初期化子の型不一致(initializer type mismatch)

    printDouble(x) // OK
    printDouble(xInt) // エラー:引数の型不一致(argument type mismatch)
    printDouble(xFloat) // エラー:引数の型不一致(argument type mismatch)
}

すべての数値型は、他の数値型への変換をサポートしています。 数値を別の型に変換するには、明示的な変換関数を使用します:

  • toByte()
  • toShort()
  • toInt()
  • toLong()
  • toFloat()
  • toDouble()

例えば、以下のコードは Int 値を Double に変換します:

kotlin
fun main() {
    val intValue: Int = 1
    val doubleValue = intValue.toDouble()
    
    println(doubleValue) // 1.0
}

浮動小数点値を整数型に変換する場合、コンパイラは小数部分を切り捨てます:

kotlin
fun main() {
    val d: Double = 1.5
    val l: Long = d.toLong()
    
    println(l) // 1
}

混合数値式

Kotlinは代入や関数の引数における暗黙的な変換をサポートしていません。 しかし、算術式の中で異なる数値型を組み合わせることはできます。その場合、Kotlinはオペランドの型に基づいて結果の型を決定し、算術演算子が自動的に変換を処理します:

kotlin
val intNumber: Int = 1
val longNumber: Long = 1000
val result = intNumber + longNumber // 1001, Long

結果をより小さな型に代入しようとすると、コンパイラはエラーを報告します:

kotlin
val intNumber: Int = 1
val longNumber: Long = 1000
val result: Int = intNumber + longNumber 
// エラー:初期化子の型不一致(Initializer type mismatch)

データオーバーフロー

数値型は、定義された範囲内の値のみを表現できます。

演算の結果がその範囲外になった場合、オーバーフローが発生します。 値をより小さな数値型に変換した場合、変換後の値が元の数値を保持できない場合があります。

この動作は、コンパイラがそれを受け入れたとしても、コードの結果に影響を与える可能性があります。

演算におけるオーバーフロー

各整数型は、定義された範囲内の値のみを格納できます。算術演算の結果がその範囲を超えると、「データオーバーフロー(data overflow)」が発生します:

kotlin
fun main(){
    val intNumber: Int = 2147483647
    // Intの最大値は 2147483647
    println(intNumber + 1) // -2147483648
}

ここでは、値が Int に収まらなくなったため、結果がラップアラウンド(最小値に循環)しています。

整数オーバーフローが発生しても、コンパイラは自動的にエラーを出すことはありません。

符号反転におけるオーバーフロー

符号反転(否定)の間にもオーバーフローが発生する可能性があります。 例えば、Int.MIN_VALUE の正の対応する値を Int として表現することはできません。

kotlin
fun main(){
    val min = Int.MIN_VALUE
    println(-min) // -2147483648
}

縮小変換

値をより小さな整数型に変換すると、結果が元の数値を保持できない場合があります:

kotlin
fun main() {
    val large: Int = 130
    val narrowed: Byte = large.toByte()

    println(narrowed) // -126
}

ただし、浮動小数点型は IEEE 754 標準に従っているため、非常に大きな結果は Infinity(無限大)になります:

kotlin
fun main() {
    println(Double.MAX_VALUE * 2) // Infinity
}

ビット演算

Kotlinは IntLong に対して「ビット演算」を提供しています。これらの演算は、一連の中置関数(infix functions)および inv() で表されます。

kotlin
fun main() {
    val x = 1
    
    println(x shl 2) // 4 
    println(x and 0x000FF000) // 0
}

ビット演算には以下が含まれます:

  • shl() – 符号付き左シフト
  • shr() – 符号付き右シフト
  • ushr() – 符号なし右シフト
  • and() – ビット単位の AND
  • or() – ビット単位の OR
  • xor() – ビット単位の XOR
  • inv() – ビット単位の反転

浮動小数点数の比較

Kotlinにおける浮動小数点数の比較は、オペランドの静的な型に依存します。

オペランドが Float または Double であることが静的にわかっている場合、数値およびそれらが形成する範囲に対する演算は IEEE 754 浮動小数点演算標準に従います。

しかし、ジェネリックなユースケース(AnyComparable<...>Collection<T> など)において、静的に浮動小数点型として型付けされていないオペランドに対しては挙動が異なります。これらの場合、Kotlinは FloatDouble のための equals() および compareTo() 実装を使用します。

その結果:

  • NaN は自分自身と等しいとみなされます
  • NaNPOSITIVE_INFINITY を含む他のどの要素よりも大きいとみなされます
  • -0.00.0 よりも小さいとみなされます

以下の例は、静的に浮動小数点型として型付けされたオペランドと、ジェネリック型を通じて使用されるオペランドの違いを示しています:

kotlin
fun generalizedEquals(a: Any, b: Any): Boolean {
    return a == b
}

fun main() {
    // 静的に浮動小数点型として型付けされたオペランド
    println(Double.NaN == Double.NaN) // false
    println(0.0 == -0.0) // true

    // 浮動小数点ではない静的型を通じて使用されるオペランド
    println(generalizedEquals(Double.NaN, Double.NaN)) // true
    println(generalizedEquals(0.0, -0.0)) // false
}

JVMにおける数値のボックス化とキャッシュ

JVMでは、Null 許容ではない数値は通常、intlongdouble などのプリミティブ型を使用して格納されます。 しかし、ジェネリック型を使用する場合や、Int? のような Null 許容の数値型を使用する場合、値はボックス化され、オブジェクトとして表現されます。

JVMは、小さな数値に対してボックス化された表現をキャッシュすることで、メモリ最適化手法を適用します。その結果、同じ値を持つボックス化された数値は 参照の等価性が認められる場合があります。

例えば、JVMは -128 から 127 までの範囲のボックス化された Integer 値をキャッシュします。そのため、以下のコードの結果は true になります:

kotlin
fun main() {
    val score: Int = 100
    val savedScore: Int? = score
    val displayedScore: Int? = score
    
    println(savedScore === displayedScore) // true
}

キャッシュされる範囲外の値の場合、ボックス化された値は個別のオブジェクトになります。その場合、値が 構造的に等しくても、参照の等価性は認められません。そのため、数値の比較には == を使用してください:

kotlin
fun main() {
    val score: Int = 10000
    val savedScore: Int? = score
    val displayedScore: Int? = score

    println(savedScore === displayedScore) // false
    println(savedScore == displayedScore) // true
}