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 округляє до найближчого парного (так зване banker’s 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, то ви не самі. Це один із тих моментів, коли важливо не «вгадувати», а памʼятати правило. Програмування — це не математика.
2. 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() — до нуля.
Таблиця для закріплення
|
|
|
|
|
|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Тут floor/ceil дотримуються математичних означень, round округляє до найближчого з правилом «половинки до парного», а toInt() робить перетворення з відкиданням дробової частини до нуля.
3. Практичні прийоми округлення
Як отримати 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() — це не «універсальне округлення», а окремий смисловий крок.
4. Типові помилки під час округлення та перетворення в 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). Тримайте коефіцієнт масштабу як частину правила, а не як випадкове число «зі стелі».
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ