1. Типы Double и Float и их литералы
Когда вы только начинаете программировать, кажется, что Int достаточно для всего: посчитать сумму, вычесть, умножить, сравнить. Но как только в задаче появляется дробная часть (проценты, средние значения, измерения, скорость, температура, “половинка” чего-нибудь), Int начинает вести себя как очень строгий бухгалтер: “дробей не существует, вот вам целое число — и не спорьте”.
Вещественные числа нужны там, где дробная часть — не украшение, а смысл. Например, “средняя скорость 37.5 км/ч”, “скидка 12.5%”, “температура 36.6”, “плотность 1.225” — всё это плохо выражается целыми числами. Теоретически можно хранить “36.6” как “366” и помнить, что это десятые, но это уже договорённость, которую легко забыть. Мы даже будем это использовать как учебный приём (например, хранить деньги в копейках), но только осознанно.
В Kotlin (как и во многих языках) для дробных вычислений есть отдельные типы. Сегодня нас интересуют два: Double и Float. Они похожи по смыслу (“числа с дробной частью”), но отличаются точностью и тем, как часто вы будете их выбирать в обычных задачах.
Double — основной выбор для дробей
Double — это главный рабочий инструмент для вещественных чисел в Kotlin. Его часто берут “по умолчанию”, когда требуется дробная часть и нет специфических ограничений. На практике вы будете встречать Double постоянно: в формулах, в расчётах, в геометрии, в процентах и почти во всём, где есть “нецелые” результаты.
У Double есть очень удобный признак: если вы пишете число с точкой, Kotlin обычно воспринимает его как Double. Например, в документации Kotlin можно увидеть пример val PI = 3.14 — тип у такого значения выводится автоматически.
Давайте закрепим базовую мысль: литерал с точкой (10.0, 2.5, 3.14) — это Double.
Мини-пример — просто посмотрим на вычисления и вывод:
fun main() {
val pi = 3.14
val radius = 2.0
val area = pi * radius * radius
println("pi = $pi") // pi = 3.14
println("area = $area") // area = 12.56
}
Обратите внимание: я специально написал radius = 2.0, а не 2. Это не “для красоты”, а чтобы сразу работать в мире дробных чисел и не получать неожиданностей при делении (мы к этому подойдём в следующем разделе).
Float — тоже дробный тип, но литерал требует суффикс f
Float — это тоже вещественное число, только обычно менее точное и более компактное. Новичку важно понять не столько “сколько бит”, сколько два прикладных факта: во‑первых, Float встречается реже в учебных задачах, а во‑вторых, Kotlin не даёт случайно записать Float литералом без специальной пометки.
Главная мысль: если вы хотите литерал типа Float, вы должны добавить суффикс f. Например, 2.5f. Это не прихоть Kotlin, а способ заставить вас быть честными: “я действительно хочу Float, я подумал, я согласен”.
Такой стиль записи встречается и в примерах Kotlin-документации: например, -3.4f — это именно Float.
Мини-пример:
fun main() {
val a: Float = 2.5f
val b: Double = 2.5
println("a = $a") // a = 2.5
println("b = $b") // b = 2.5
}
И еще важный момент, который вы почти наверняка словите в первые дни: если вы напишете val x: Float = 2.5 без f, компилятор будет недоволен. Он видит 2.5 как Double, а Kotlin не любит “тихие” числовые превращения. Это хорошо: меньше магии — меньше неожиданных багов.
Пока что запомните простое правило: если не уверены, что вам нужен Float, берите Double. Float чаще используется в задачах вроде графики или больших массивов чисел, где важна экономия памяти, но в наших первых программах это обычно не критично.
Литералы: 10 и 10.0 — это разные типы
Сейчас будет момент, где мозг любит делать “ага, понятно” — и тут же ошибаться на следующей строке. В Kotlin два визуально похожих числа могут быть разными типами просто потому, что у одного есть точка, а у другого нет. 10 — это целое число (обычно Int), а 10.0 — уже дробное (Double).
Это важно потому что тип влияет на то, как работают операции с числами. Особенно деление. Особенно, когда вы ждёте дробный результат, а получаете целый и думаете: “Кто украл мои 0.5?”
Мини-пример:
fun main() {
val a = 10 // Int (целое)
val b = 10.0 // Double (вещественное)
println("a = $a") // a = 10
println("b = $b") // b = 10.0
}
Да, вывод уже намекает: 10.0 выглядит как число с дробной частью (даже если дробь нулевая). Это полезно: вы визуально видите, что работаете в другом “режиме”.
Отсюда простой (и очень рабочий) приём: если вы хотите “перевести вычисления в мир дробей”, достаточно где-то в выражении поставить .0. Это звучит слишком просто, но именно так часто и делают в базовых задачах, пока не перешли к другим преобразованиям типов.
3. Деление: целые против вещественных
Деление — главный источник удивления у новичков. Причём удивление честное: “Я же в школе делил, почему тут иначе?” Ответ простой: компьютер выполняет деление с учетом типов. Если вы делите целое на целое, результат тоже целый. Дробная часть в таком делении просто отбрасывается.
И это поведение не “ошибка Kotlin”, а стандартная арифметика целых чисел в программировании: Int / Int -> Int. Чтобы получить дробный результат, хотя бы один операнд должен быть Double или Float.
Мини-пример, который стоит запомнить как пароль от двери “в мир дробей”:
fun main() {
val a = 5 / 2
val b = 5.0 / 2
println("a = $a") // a = 2
println("b = $b") // b = 2.5
}
Теперь давайте попробуем написать небольшую бизнес-логику. Пусть у нас будет расчёт чека, но чтобы не заниматься вводом вещественных чисел (это отдельная тема), мы будем хранить деньги в центах как Int, а для вывода переводить в евро через деление на 100.0.
Обратите внимание: делим на 100.0, а не на 100. Это принципиально.
fun main() {
val totalCents = 2599 // 25.99 (условных евро/долларов — неважно)
val total = totalCents / 100.0 // Double: 25.99
println("totalCents = $totalCents") // totalCents = 2599
println("total = $total") // total = 25.99
}
Если бы мы написали totalCents / 100, получилось бы 25, и вы бы потеряли 99 центов. Программа бы радостно “сэкономила” пользователю деньги, но, к сожалению, только в консоли.
Чтобы это закрепить, добавим ещё одну типичную операцию: разделить счёт на нескольких людей. Количество людей пусть вводится как целое, это нам знакомо.
fun main() {
val totalCents = 2599
val people = 3
val total = totalCents / 100.0
val perPerson = total / people
println("total = $total") // total = 25.99
println("perPerson = $perPerson") // perPerson = 8.663333333333334
}
Да, вывод “длинный”. Это нормально: мы пока не округляем и не форматируем красиво. Наша цель сейчас — понять, почему вообще получилось не целое число и как добраться до дробного результата. Красота вывода будет позже, сегодня мы “учимся ходить”, а не “танцевать”.
Небольшая схема‑напоминалка, как думать про деление:
flowchart TD
A["Вы пишете деление x / y"] --> B{"x и y — целые Int?"}
B -- "да" --> C["Результат тоже Int дробная часть теряется"]
B -- "нет (есть Double/Float)" --> D["Результат вещественный дробная часть сохраняется"]
Если вы в сомнениях, просто задайте себе вопрос: “В этом выражении есть хотя бы одно число с точкой?” Если нет — велика вероятность целочисленного поведения.
4. Научная запись: 1e3 и 1e-3
Когда числа становятся очень большими или очень маленькими, писать их “в лоб” неудобно. Никто не хочет читать 0.000001 и считать нули глазами, особенно когда глаза и так устали после десятого println. Для таких случаев есть научная запись (её ещё называют экспоненциальной): 1e3 означает “1 умножить на 10 в степени 3”.
В Kotlin такие литералы тоже вещественные числа (обычно Double). Это удобно: вы можете записывать коэффициенты, допуски, масштабы и прочие “маленькие/большие” значения коротко и читаемо.
Мини-пример:
fun main() {
val big = 1e3 // 1000.0
val small = 1e-3 // 0.001
println("big = $big") // big = 1000.0
println("small = $small") // small = 0.001
}
Почему это полезно именно вам, новичкам? Потому что в формулах часто встречаются масштабирующие коэффициенты. Например, “перевести миллиметры в метры” — это умножить на 1e-3. “перевести килограммы в граммы” — умножить на 1e3. И такая запись гораздо меньше провоцирует ошибку “не туда поставил ноль”.
При этом не пугайтесь, если Kotlin иногда выводит число обратно в виде 1.0E-3 или чем-то похожим. Это просто формат отображения. Число то же самое, просто напечатано компактнее.
5. Что выбрать: Double или Float
Вам хочется получить чёткое правило, чтобы не страдать? У меня для вас хорошая новость: на уровне наших задач оно действительно простое. Если вы пишете обычную консольную программу, считаете проценты, среднее, переводите единицы измерения, вычисляете площадь круга или делите чек на друзей, выбирайте Double. Это наиболее привычный и “широкий” вариант.
Float стоит брать, когда у вас есть на это конкретная причина. Чаще всего причина звучит как “у нас очень много чисел, и мы экономим память” или “мы работаем в среде, где принято использовать Float (например, некоторые графические расчёты)”. Но эти истории обычно начинаются позже, когда вы уже уверенно пишете код и понимаете, где именно вам это надо.
Если хочется понадежнее зафиксировать это в своей голове, можно пользоваться простой таблицей (не как шпаргалкой для экзамена, а как напоминанием “что я вообще делаю”):
| Тип | Как выглядит литерал | Пример | Когда нужен в начале |
|---|---|---|---|
|
без точки | |
счётчики, количество, индексы |
|
с точкой / e | |
дробные расчёты “по умолчанию” |
|
с точкой + f | |
редко; только если есть причина |
И последнее важное правило, которое сегодня можно считать “железным”: если вам нужен дробный результат — убедитесь, что выражение вычисляется как Double (или Float). Самый простой путь — использовать хотя бы один литерал с точкой: 100.0, 5.0, 2.0. Это звучит почти как лайфхак, но на первых порах он спасает от огромного количества ошибок.
6. Типичные ошибки
Ошибка №1: ожидать, что 5 / 2 даст 2.5.
Это классика. Проблема в том, что оба числа — целые, а значит результат тоже целый. Лечится очень просто: хотя бы одно число делаем вещественным, например 5.0 / 2 или 5 / 2.0. Полезная привычка — в задачах со средними и процентами сразу писать “базовые” числа как Double (с точкой), чтобы не забыть.
Ошибка №2: пытаться присвоить Float без суффикса f.
val x: Float = 2.5 выглядит логично по-человечески, но Kotlin видит 2.5 как Double и не хочет “молча” превращать одно в другое. Правильная запись: 2.5f. Если вы не уверены, что вам нужен именно Float, лучше вообще не трогать его и взять Double — так меньше поводов спорить с компилятором.
Ошибка №3: делить деньги/проценты целочисленно и “терять хвост”.
Когда вы храните деньги в копейках (Int) и переводите в швейцарские франки, важно делить на 100.0, а не на 100. Деление на 100 даст целое число и выбросит остаток. Эта ошибка особенно коварна тем, что программа работает “нормально”, просто результат становится неправильным, а неправильность не всегда заметна сразу.
Ошибка №4: пугаться научной записи и воспринимать её как “другой тип”.
1e-3 — это не особый секретный тип, а обычный литерал вещественного числа. Он означает “умножить на 10 в степени -3”. Если в выводе вы увидели формат вроде 1.0E-3, это тоже нормально: Kotlin (точнее, стандартный вывод чисел) иногда печатает маленькие значения в экспоненциальном виде, чтобы не рисовать много нулей.
Ошибка №5: выбирать Float “просто потому что короче”.
Иногда кажется, что Float — это “более лёгкий Double”, значит лучше. На практике в учебных задачах это чаще создаёт лишние сложности: литералы требуют f, ошибки типов появляются чаще, а выигрыша вы не чувствуете. Поэтому пока действует спокойное правило: по умолчанию Double, Float — только при осознанной необходимости.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ