Числа
Числовые типы Kotlin представляют:
Используйте числовые типы для хранения и обработки числовых данных, например, в арифметических операциях, счетчиках, измерениях и других вычислениях.
Выбор числового типа
В большинстве случаев для выбора подходящего для вашей задачи числового типа можно руководствоваться следующими правилами:
Используйте
Intдля целых чисел.Используйте
Longдля целых чисел, выходящих за пределы диапазонаInt.Используйте
Doubleдля десятичных чисел.Используйте
Float, если допустима или требуется меньшая точность.Используйте
ByteиShort, если этого требует API или формат данных.
Целочисленные типы
В Kotlin есть четыре целочисленных типа с разными размерами и диапазонами значений:
Тип |
Размер (в битах) |
Минимальное значение |
Максимальное значение |
|---|---|---|---|
|
8 |
-128 |
127 |
|
16 |
-32768 |
32767 |
|
32 |
-2,147,483,648 (-231) |
2,147,483,647 (231 - 1) |
|
64 |
-9,223,372,036,854,775,808 (-263) |
9,223,372,036,854,775,807 (263 - 1) |
Объявление целочисленных значений
Kotlin поддерживает следующие формы записи целочисленных литералов:
Десятичные:
123Шестнадцатеричные:
0x0FДвоичные:
0b00001011
Чтобы объявить числовое значение, укажите тип явно:
val one: Int = 1 // Use underscores to improve readability val oneBillion: Long = 1_000_000_000 val hexBytes: Int = 0x7F_EC_DE_5E val bytes: Int = 0b01010010_01101001_10010100_10010010 val oneByte: Byte = 1 val oneShort: Short = 1
Чтобы объявить значение типа Long, можно также добавить суффикс L:
val oneLong = 1L
Если числовой тип указан явно, компилятор проверяет, что значение входит в диапазон этого типа:
// Value fits in Byte val oneByte: Byte = 1 // Error: the value does not fit in Byte val tooBig: Byte = 128
Если числовой тип не указан, Kotlin выводит тип Int, если значение входит в диапазон Int. В противном случае Kotlin выводит тип Long:
val million = 1_000_000 // Int val threeBillion = 3_000_000_000 // Long
Если значение может отсутствовать, используйте nullable-типы:
val maybeAbsent: Int? = null
Типы с плавающей точкой
Для чисел с дробной частью в Kotlin предусмотрены типы Float и Double.
Типы с плавающей точкой соответствуют стандарту IEEE 754. Float обеспечивает одинарную точность. Double обеспечивает двойную точность.
Типы с плавающей точкой различаются размером и точностью:
Тип |
Размер (в битах) |
Значащие биты |
Биты экспоненты |
Десятичные цифры |
|---|---|---|---|---|
|
32 |
24 |
8 |
6-7 |
|
64 |
53 |
11 |
15-16 |
Объявление значений с плавающей точкой
Чтобы объявить литерал с плавающей точкой, добавьте десятичную точку (.) или используйте экспоненциальную запись:
val pi = 3.14 val avogadro = 6.02214076e23
По умолчанию Kotlin выводит для литералов с плавающей точкой тип Double. Чтобы объявить значение типа Float, добавьте суффикс f или F:
val pi = 3.14 // Double val eFloat = 2.7182817f // Float
Если значение может отсутствовать, используйте nullable-типы:
val maybeAbsent: Double? = null
Арифметические операции
Kotlin поддерживает стандартные арифметические операции над числами: +, -, *, / и %.
Используйте эти операторы для выполнения распространенных вычислений:
fun main() {
//sampleStart
println(1 + 2) // 3
println(2_500_000_000L - 1L) // 2499999999
println(3.14 * 2.71) // 8.5094
println(10.0 / 3) // 3.3333333333333335
//sampleEnd
}
Тип результата зависит от типов операндов. Подробнее см. в разделе Смешанные числовые выражения.
Целочисленное деление
При делении целочисленных значений результат всегда является целым числом. Компилятор отбрасывает дробную часть:
fun main() {
//sampleStart
val intValue = 5 / 2
println(intValue) // 2
val longValue = 5L / 2
println(longValue) // 2
//sampleEnd
}
Чтобы получить результат с плавающей точкой, задайте хотя бы один операнд как Float или Double:
fun main() {
//sampleStart
val a = 5 / 2.0
println(a) // 2.5
val b = 5 / 2.toDouble()
println(b) // 2.5
//sampleEnd
}
Преобразование типов
Числовые типы не являются подтипами друг друга. Чтобы избежать незаметной потери данных и неожиданного поведения, Kotlin требует явного преобразования типов.
Например, функция, ожидающая значение типа Double, не может принимать значение типа Int или Float без преобразования:
fun main() {
//sampleStart
fun printDouble(x: Double) {
print(x)
}
val x = 1.0
val xInt = 1
val xFloat = 1.0f
val one: Double = 1 // Error: initializer type mismatch
printDouble(x) // OK
printDouble(xInt) // Error: argument type mismatch
printDouble(xFloat) // Error: argument type mismatch
//sampleEnd
}
Все числовые типы поддерживают преобразование в другие числовые типы. Чтобы преобразовать число в другой тип, используйте явную функцию преобразования:
toByte()toShort()toInt()toLong()toFloat()toDouble()
Например, следующий код преобразует значение типа Int в Double:
fun main() {
//sampleStart
val intValue: Int = 1
val doubleValue = intValue.toDouble()
println(doubleValue) // 1.0
//sampleEnd
}
При преобразовании значения с плавающей точкой в целочисленный тип компилятор отбрасывает дробную часть:
fun main() {
//sampleStart
val d: Double = 1.5
val l: Long = d.toLong()
println(l) // 1
//sampleEnd
}
Смешанные числовые выражения
Kotlin не поддерживает неявное преобразование типов при присваивании или передаче аргументов функции. Однако в арифметических выражениях можно сочетать разные числовые типы. В таких случаях Kotlin определяет тип результата на основе типов операндов, а арифметические операторы автоматически выполняют преобразование:
val intNumber: Int = 1 val longNumber: Long = 1000 val result = intNumber + longNumber // 1001, Long
Если попытаться присвоить результат переменной меньшего типа, компилятор сообщит об ошибке:
val intNumber: Int = 1 val longNumber: Long = 1000 val result: Int = intNumber + longNumber // Error: Initializer type mismatch
Типы целочисленных литералов
При выводе типа Kotlin рассматривает целочисленные литералы без суффикса как специальный тип целочисленного литерала (ILT), пока окружающий контекст не определит конкретный тип:
//sampleStart
fun List<Any>.log() {
println(joinToString(" | ") { it::class.simpleName ?: "Unknown" })
}
fun main() {
listOf(1, 2).log()
// Int | Int
listOf(1L, 2L).log()
// Long | Long
// Compiler interprets 1 as an ILT and resolves it to Long
listOf(1, 2L).log()
// Long | Long
// .toInt() converts the literal to Int
listOf(1.toInt(), 2L).log()
// Int | Long
}
//sampleEnd
Эту особенность особенно легко не заметить со значениями Int и Long, поскольку во время выполнения они имеют одинаковое строковое представление. Чтобы этого избежать, укажите ожидаемый тип или преобразуйте значения явно:
//sampleStart
fun List<Any>.log() {
println(joinToString(" | ") { it::class.simpleName ?: "Unknown" })
}
fun main() {
val longValues: List<Long> = listOf(1, 2L)
longValues.log()
// Long | Long
val numberValues: List<Number> = listOf(1.toInt(), 2L)
numberValues.log()
// Int | Long
}
//sampleEnd
Явный тип также можно использовать, чтобы обнаружить непреднамеренный вывод типа:
fun main() {
//sampleStart
val intValues: List<Int> = listOf(1, 2L)
// Error: initializer type mismatch
//sampleEnd
}
Переполнение данных
Числовые типы могут представлять только значения в пределах определенных для них диапазонов.
Если результат операции выходит за эти пределы, происходит переполнение. При преобразовании значения в числовой тип меньшего размера преобразованное значение может отличаться от исходного.
Такое поведение может повлиять на результат выполнения кода, даже если компилятор его принимает.
Переполнение при выполнении операций
Каждый целочисленный тип может хранить только значения в пределах заданного диапазона. Если результат арифметической операции выходит за эти пределы, происходит переполнение данных:
fun main(){
//sampleStart
val intNumber: Int = 2147483647
// Max Int value is 2147483647
println(intNumber + 1) // -2147483648
//sampleEnd
}
Здесь происходит циклический переход к началу диапазона, поскольку значение не помещается в Int.
Переполнение при отрицании
Переполнение также может произойти при отрицании. Например, положительное значение, противоположное Int.MIN_VALUE, нельзя представить типом Int.
fun main(){
//sampleStart
val min = Int.MIN_VALUE
println(-min) // -2147483648
//sampleEnd
}
Преобразования с сужением диапазона
При преобразовании значения в целочисленный тип меньшего размера результат может отличаться от исходного значения:
fun main() {
//sampleStart
val large: Int = 130
val narrowed: Byte = large.toByte()
println(narrowed) // -126
//sampleEnd
}
Однако, поскольку типы с плавающей точкой соответствуют стандарту IEEE 754, очень большие результаты могут стать Infinity:
fun main() {
//sampleStart
println(Double.MAX_VALUE * 2) // Infinity
//sampleEnd
}
Побитовые операции
В Kotlin доступны побитовые операции для Int и Long. Эти операции представлены набором инфиксных функций и inv().
fun main() {
//sampleStart
val x = 1
println(x shl 2) // 4
println(x and 0x000FF000) // 0
//sampleEnd
}
К побитовым операциям относятся:
shl()— знаковый сдвиг влевоshr()— знаковый сдвиг вправоushr()— беззнаковый сдвиг вправоand()— побитовое Иor()— побитовое ИЛИxor()— побитовое исключающее ИЛИinv()— побитовая инверсия
Сравнение чисел с плавающей точкой
В Kotlin способ сравнения чисел с плавающей точкой зависит от статического типа операндов.
Если статически известно, что операнды имеют тип Float или Double, операции над числами и образуемый ими диапазон соответствуют стандарту IEEE 754 для арифметики с плавающей точкой.
Однако в обобщенных сценариях (например, при использовании Any, Comparable<...> или Collection<T>) поведение операндов, статический тип которых не является типом с плавающей точкой, отличается. В таких случаях Kotlin использует реализации equals() и compareTo() для Float и Double.
В результате:
NaNсчитается равным самому себеNaNсчитается больше любого другого элемента, включаяPOSITIVE_INFINITY-0.0считается меньше0.0
В следующем примере показано различие между операндами со статическим типом числа с плавающей точкой и операндами, используемыми через обобщенные типы:
//sampleStart
fun generalizedEquals(a: Any, b: Any): Boolean {
return a == b
}
fun main() {
// Operands statically typed as floating-point numbers
println(Double.NaN == Double.NaN) // false
println(0.0 == -0.0) // true
// Operands used through a non-floating-point static type
println(generalizedEquals(Double.NaN, Double.NaN)) // true
println(generalizedEquals(0.0, -0.0)) // false
}
//sampleEnd
Упаковка и кэширование чисел в JVM
В JVM значения числовых типов, допускающих значение null, обычно хранятся с использованием примитивных типов, таких как int, long или double. Однако при использовании обобщенных типов или числовых nullable-типов, например Int?, значение упаковывается и представляется в виде объекта.
JVM применяет к небольшим числам технику оптимизации памяти, кэшируя их упакованные представления. В результате упакованные числа с одинаковыми значениями могут быть ссылочно равны.
Например, JVM кэширует упакованные значения Integer в диапазоне от -128 до 127. Поэтому следующий код возвращает true:
fun main() {
//sampleStart
val score: Int = 100
val savedScore: Int? = score
val displayedScore: Int? = score
println(savedScore === displayedScore) // true
//sampleEnd
}
Для значений за пределами кэшируемого диапазона упакованные значения являются отдельными объектами. В этом случае они не будут ссылочно равны, даже если их значения структурно равны. Поэтому для сравнения числовых значений используйте ==:
fun main() {
//sampleStart
val score: Int = 10000
val savedScore: Int? = score
val displayedScore: Int? = score
println(savedScore === displayedScore) // false
println(savedScore == displayedScore) // true
//sampleEnd
}
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/numbers.html