JavaRush /Курсы /Kotlin SELF /Округление floor/ceil/round и отличие от toInt()

Округление floor/ceil/round и отличие от toInt()

Kotlin SELF
6 уровень , 3 лекция
Открыта

1. Базовые функции: floor, ceil, round

Если вы только начали программировать, кажется, что округление — это что-то «для бухгалтеров» или «для Excel». На практике оно появляется намного раньше: как только вы начали делить числа и получать дроби, вы почти сразу захотите либо красиво вывести результат, либо применить правило вроде «округлять вверх», либо посчитать количество целых единиц, которые покрывают дробный объём.

Представьте типичный бытовой сценарий. У вас есть длина комнаты 3.2 метра, а вы покупаете плинтус по 1 метру. Вам нужно 4 штуки, а не 3 (то есть округление вверх). Или вы считаете скидку и хотите показать цену с двумя знаками после запятой. Или вы вычисляете «сколько страниц осталось», и дробные страницы никого не радуют. В программировании это всё происходит постоянно — и именно поэтому в стандартной библиотеке Kotlin есть целый набор функций округления в пакете kotlin.math.

floor(x): округление вниз из математики

Когда говорят «округлить вниз», новички часто слышат «просто отбрось дробь». Это опасная путаница. floor(x) округляет число к меньшему целому (то есть «вниз по числовой прямой», в сторону минус бесконечности). По определению, floor(x) — это округление к целому в сторону отрицательной бесконечности.

Почему это важно? Потому что на положительных числах «округление вниз» и «отбрасывание дроби» выглядят одинаково, а на отрицательных — внезапно расходятся. И именно отрицательные числа чаще всего «вскрывают» ошибки в вашем понимании.

Мини-пример: floor на положительных и отрицательных

import kotlin.math.floor

fun main() {
    println(floor(3.14159))   // 3.0
    println(floor(10.0))      // 10.0
    println(floor(-1.1))      // -2.0
}

Обратите внимание: floor(-1.1) даёт -2.0, потому что -2 меньше, чем -1.1, а -1 — больше. Это и есть «вниз по оси».

ceil(x): округление вверх из математики

Теперь симметричная операция. ceil(x) округляет число к большему целому (то есть «вверх по числовой прямой», к плюс бесконечности). Формально это округление в сторону положительной бесконечности.

Очень полезная функция, когда вы считаете количество целых единиц, чтобы «хватило наверняка»: сколько коробок нужно, сколько автобусов нужно, сколько полных метров материала нужно купить. Если floor — это «сколько целых точно помещается», то ceil — «сколько целых нужно, чтобы покрыть всё».

Мини-пример: ceil на положительных и отрицательных

import kotlin.math.ceil

fun main() {
    println(ceil(3.14159))    // 4.0
    println(ceil(10.0))       // 10.0
    println(ceil(-1.1))       // -1.0
}

Заметьте, как ведёт себя отрицательное: ceil(-1.1) = -1.0, потому что -1 больше, чем -1.1.

round(x): округление до ближайшего

Самое «привычное» округление — «до ближайшего целого». В Kotlin есть функция round(x), которая округляет к ближайшему целому, но у неё есть важный нюанс: если число ровно посередине (например, 2.5), Kotlin округляет к ближайшему чётному (так называемое bankers rounding).

Звучит немного как «математика решила поиграть в справедливость», но логика в этом есть: при больших объёмах данных такой способ уменьшает накопление систематической ошибки (не «всегда вверх» и не «всегда вниз»).

Мини-пример: round и «половинки»

import kotlin.math.round

fun main() {
    println(round(3.49))   // 3.0
    println(round(3.51))   // 4.0
    println(round(3.5))    // 4.0  (между 3 и 4 -> к чётному 4)
    println(round(2.5))    // 2.0  (между 2 и 3 -> к чётному 2)
}

Если вы ожидали, что 2.5 станет 3.0, то вы не одиноки. Это один из тех моментов, когда важно не «угадывать», а помнить правило. Программирование — это не математика.

3. toInt() и почему это не округление

floor/ceil/round возвращают Double

Иногда кажется, что если мы округлили число до целого, то и тип должен стать Int. Но функции floor, ceil, round работают в мире вещественных чисел и возвращают Double (или Float для Float-версий). Это удобно: вы можете продолжать вычислять сложные выражения как Double и только в конце, если нужно, переводить результат в Int.

Здесь полезно запомнить простую «ментальную модель»: округление — это математическая операция, а toInt() — это преобразование типа. И это разные сущности, даже если иногда дают одинаковые цифры.

toInt() отбрасывает дробь к нулю

Вот главный герой сегодняшней ловушки. Метод toInt() у Double делает не «математическое округление», а преобразование: дробная часть отбрасывается в сторону нуля.

То есть 3.99.toInt() станет 3, а -3.99.toInt() станет -3. Не -4. И это очень частый источник «почему у меня поехали расчёты при отрицательных числах».

Мини-пример: toInt() на плюсах и минусах

fun main() {
    println(3.99.toInt())     // 3
    println(-3.99.toInt())    // -3
    println(3.0.toInt())      // 3
}

Если вы сейчас подумали «ну да, обрезает дробь», то уточнение такое: обрезает к нулю, а не вниз по оси. Это и есть ключевой момент.

Нюанс: NaN и очень большие числа

toInt() ещё и защищает вас от некоторых крайностей. Если значение NaN, то вернётся 0. Если число слишком большое или слишком маленькое для Int, то результат превратится в Int.MAX_VALUE или Int.MIN_VALUE.

Мы не будем сегодня углубляться в эти случаи, но вам полезно знать, что toInt() — не просто «отрезать дробь», а ещё и «влезть в диапазон Int».

Где именно toInt() расходится с floor/ceil/round

Если вы возьмёте положительное число 7.9, то:

  • floor(7.9) = 7.0
  • 7.9.toInt() = 7

И легко ошибиться, решив, что это одно и то же.

А теперь возьмём отрицательное -7.9:

  • floor(-7.9) = -8.0
  • (-7.9).toInt() = -7

Разница принципиальная: floor идёт к минус бесконечности, toInt() — к нулю.

Таблица для закрепления

x
floor(x)
ceil(x)
round(x)
x.toInt()
3.2
3.0
4.0
3.0
3
3.8
3.0
4.0
4.0
3
-3.2
-4.0
-3.0
-3.0
-3
-3.8
-4.0
-3.0
-4.0
-3

Здесь floor/ceil следуют математическим определениям, round округляет к ближайшему с «половинками к чётному», а toInt() делает преобразование с отбрасыванием дроби к нулю.

4. Практические приёмы округления

Как получить Int по нужному правилу

Часто вам нужен именно Int: например, количество предметов, количество страниц, количество упаковок. Но округлять вы хотите по правилу floor/ceil/round, а не «к нулю».

Тогда используем простой приём: сначала округляем как Double, затем переводим в Int. Да, в два шага. Да, это нормально. Kotlin не обидится.

Пример: округлить вверх и получить Int

import kotlin.math.ceil

fun main() {
    val metersNeeded = 3.2
    val pieces = ceil(metersNeeded).toInt()

    println(pieces) // 4
}

Пример: округлить вниз и получить Int

import kotlin.math.floor

fun main() {
    val fullBoxes = 9.99
    val boxes = floor(fullBoxes).toInt()

    println(boxes) // 9
}

Пример: округлить до ближайшего и получить Int

import kotlin.math.round

fun main() {
    val rating = 4.6
    val rounded = round(rating).toInt()

    println(rounded) // 5
}

Округление до N знаков после запятой

Очень часто возникает задача — округлить до 2 знаков (цены, проценты). Но round(x) округляет до целого. Как быть? Используем школьный трюк, который на практике встречается постоянно: умножаем на 10N, округляем до целого, потом делим обратно.

Важно помнить: мы всё ещё в мире Double, и из-за точности иногда можно увидеть не идеально красивую дробь. Но как базовый приём — это отличный старт.

Пример: округление до 2 знаков

import kotlin.math.round

fun main() {
    val x = 12.34567
    val rounded2 = round(x * 100.0) / 100.0

    println(rounded2) // 12.35
}

Смысл такой: x * 100 сдвигает запятую на 2 знака вправо, round делает число целым, / 100 возвращает запятую назад.

Мини-сценарий: калькулятор цены доставки

Сейчас мы напишем маленький (но хороший) проект, который связывает всё изученное. Представим, что у нас есть простая консольная программа, которая считает стоимость заказа, налог и доставку. Мы не читаем данные с консоли, поэтому используем константы в коде — нам важно именно округление.

Допустим:

  • цена товаров: subtotal
  • налог 7.5%: taxRate
  • доставка считается «за каждый начатый килограмм» (то есть округление вверх)
  • выводим итог с 2 знаками после запятой

Шаг 1: считаем налог и округляем итог для вывода

import kotlin.math.round

fun main() {
    val subtotal = 39.90
    val taxRate = 0.075

    val tax = subtotal * taxRate
    val total = subtotal + tax

    val totalPretty = round(total * 100.0) / 100.0
    println("Итого: $totalPretty") // Итого: 42.89
}

Здесь важный момент: мы округлили только для вывода. В реальных задачах правила могут быть строже (когда и как округлять), но сегодня нам достаточно понять механику.

Шаг 2: считаем доставку по правилу «за каждый начатый килограмм»

Теперь добавим вес и цену за килограмм. Если вес 2.1, платить нужно как за 3 (округление вверх).

import kotlin.math.ceil

fun main() {
    val weightKg = 2.1
    val pricePerKg = 3.0

    val billedKg = ceil(weightKg).toInt()
    val shipping = billedKg * pricePerKg

    println("Вес к оплате: $billedKg кг") // Вес к оплате: 3 кг
    println("Доставка: $shipping")        // Доставка: 9.0
}

Обратите внимание: ceil(weightKg) возвращает Double, поэтому toInt() — отдельным шагом.

Шаг 3: почему нельзя писать weightKg.toInt() вместо ceil(weightKg)

И вот здесь — момент истины. На положительных весах всё может выглядеть «почти нормально», но смысл будет неверный: 2.1.toInt() даст 2, то есть вы недосчитаете доставку.

fun main() {
    val weightKg = 2.1

    println(weightKg.toInt()) // 2  (это НЕ "за каждый начатый", это "отрезать к нулю")
}

toInt() — это не «округление вверх», и даже не «математическое округление вниз». Это преобразование с отбрасыванием дробной части к нулю.

Схема выбора округления

Когда вы в следующий раз будете выбирать между floor, ceil, round и toInt(), полезно не перебирать в голове «кажется, это то», а задать себе один вопрос: какое правило у задачи?

flowchart TD
    A["Есть Double x"] --> B{"Это для вывода человеку?"}
    B -->|Да| C["Округление до N знаков: round(x*10^N)/10^N"]
    B -->|Нет| D{"Нужно целое количество единиц?"}
    D -->|Да, чтобы ХВАТИЛО| E["ceil(x) -> toInt()"]
    D -->|Да, сколько помещается точно| F["floor(x) -> toInt()"]
    D -->|Нет, просто привести тип| G["x.toInt() (отрезает к нулю)"]

Эта схема специально напоминает: toInt() — это не «универсальное округление», а отдельный смысловой шаг.

5. Типичные ошибки при округлении и преобразовании в Int

Ошибка №1: считать, что toInt() — это округление.
На первых порах очень хочется думать: «перевёл в Int — значит округлил». На самом деле Double.toInt() отбрасывает дробную часть в сторону нуля. Из-за этого логика «округлить вниз» на отрицательных значениях ломается, а логика «округлить вверх» ломается вообще всегда.

Ошибка №2: путать “округление вниз” и “в сторону нуля”.
floor(x) округляет вниз по числовой прямой, то есть к отрицательной бесконечности. А toInt() и truncate(x) (если вы его встретите) работают к нулю. На положительных числах разницы почти не видно, поэтому ошибка прячется и «вылезает» потом в самый неудобный момент.

Ошибка №3: ожидать, что round(2.5) даст 3.0.
В Kotlin round(x) использует правило “ties to even” — половинки округляются к ближайшему чётному. Если вы пишете алгоритм, где поведение на .5 важно (например, баллы, рейтинги), обязательно проверьте, что именно ожидается по правилам задачи.

Ошибка №4: забыть, что ceil/floor/round возвращают Double, и пытаться «скормить» его туда, где ждут Int.
В Kotlin это обычно не скомпилируется (и это хорошо). Но новички иногда пытаются «продавить» идею: мол, раз 3.0, значит это же целое. Нет: тип всё ещё Double. Правильный путь — сначала округление (ceil/floor/round), потом явное преобразование (toInt()).

Ошибка №5: округлять “до 2 знаков” без масштаба или с неправильным масштабом.
Трюк round(x * 100.0) / 100.0 работает ровно потому, что 100 соответствует двум знакам. Если вы случайно умножили на 10, а делите на 100, получится странная «магия» (и это будет честно ваша магия, не Kotlin). Держите коэффициент масштаба как часть правила, а не как случайное число из воздуха.

1
Задача
Kotlin SELF, 6 уровень, 3 лекция
Недоступна
Три округления
Три округления
1
Задача
Kotlin SELF, 6 уровень, 3 лекция
Недоступна
Спор округлений
Спор округлений
1
Задача
Kotlin SELF, 6 уровень, 3 лекция
Недоступна
Упаковки склада
Упаковки склада
1
Задача
Kotlin SELF, 6 уровень, 3 лекция
Недоступна
Итог по чеку
Итог по чеку
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ