JavaRush /Courses /Kotlin SELF /Rounding with floor/ceil/round and how it differs from to...

Rounding with floor/ceil/round and how it differs from toInt()

Kotlin SELF
Level 6, Lesson 3
Available

1. Basic functions: floor, ceil, round

If you have just started programming, rounding can feel like something “for accountants” or “for Excel.” In practice, it shows up much earlier: as soon as you start dividing numbers and getting fractions, you almost immediately want either to display the result nicely, or apply a rule like “round up,” or count how many whole units cover a fractional amount.

Imagine a typical everyday scenario. You have a room length of 3.2 meters, and you buy baseboard in 1-meter pieces. You need 4 pieces, not 3 (that is, rounding up). Or you calculate a discount and want to show a price with two digits after the decimal point. Or you compute “how many pages are left,” and fractional pages make nobody happy. In programming, this happens all the time — and that is why Kotlin’s standard library includes a whole set of rounding functions in the kotlin.math package.

floor(x): mathematical rounding down

When people say “round down,” beginners often hear “just drop the fractional part.” This is a dangerous confusion. floor(x) rounds a number to the smaller integer (that is, “down the number line,” toward minus infinity). By definition, floor(x) is rounding to an integer toward negative infinity.

Why does this matter? Because for positive numbers, “rounding down” and “dropping the fraction” look the same, but for negative numbers they suddenly diverge. And it is negative numbers that most often “expose” mistakes in your understanding.

Mini example: floor on positive and negative numbers

import kotlin.math.floor

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

Notice: floor(-1.1) gives -2.0, because -2 is smaller than -1.1, while -1 is larger. That is exactly “down the axis.”

ceil(x): mathematical rounding up

Now the symmetric operation. ceil(x) rounds a number to the larger integer (that is, “up the number line,” toward plus infinity). Formally, it is rounding toward positive infinity.

A very useful function when you are counting whole units so that you “definitely have enough”: how many boxes you need, how many buses you need, how many full meters of material you need to buy. If floor is “how many whole units fit for sure,” then ceil is “how many whole units are needed to cover everything.”

Mini example: ceil on positive and negative numbers

import kotlin.math.ceil

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

Notice how the negative value behaves: ceil(-1.1) = -1.0, because -1 is greater than -1.1.

round(x): rounding to the nearest

The most “familiar” rounding is “to the nearest integer.” In Kotlin, there is the function round(x), which rounds to the nearest integer, but it has an important nuance: if the number is exactly halfway (for example, 2.5), Kotlin rounds to the nearest even (the so-called bankers rounding).

It sounds a bit like “math decided to play fair,” but there is logic: with large volumes of data, this approach reduces the accumulation of systematic error (not “always up” and not “always down”).

Mini example: round and “halves”

import kotlin.math.round

fun main() {
    println(round(3.49))   // 3.0
    println(round(3.51))   // 4.0
    println(round(3.5))    // 4.0  (between 3 and 4 -> to even 4)
    println(round(2.5))    // 2.0  (between 2 and 3 -> to even 2)
}

If you expected 2.5 to become 3.0, you are not alone. This is one of those moments where it matters not to “guess,” but to remember the rule. Programming is not mathematics.

3. toInt() and why it is not rounding

floor/ceil/round return Double

Sometimes it feels like if we rounded a number to an integer, the type should become Int as well. But floor, ceil, round operate in the world of real numbers and return Double (or Float for the Float versions). This is convenient: you can keep computing complex expressions as Double and only at the end, if needed, convert the result to Int.

It is useful to remember a simple “mental model”: rounding is a mathematical operation, while toInt() is a type conversion. These are different things, even if they sometimes produce the same digits.

toInt() truncates toward zero

Here is the main character of today’s trap. The toInt() method on Double does not do “mathematical rounding,” but conversion: the fractional part is discarded toward zero.

That means 3.99.toInt() becomes 3, and -3.99.toInt() becomes -3. Not -4. This is a very common source of “why did my calculations break for negative numbers.”

Mini example: toInt() on plus and minus values

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

If you just thought, “sure, it cuts off the fraction,” the clarification is: it cuts toward zero, not down the axis. That is the key point.

Nuance: NaN and very large numbers

toInt() also protects you from certain extremes. If the value is NaN, it returns 0. If the number is too large or too small for Int, the result becomes Int.MAX_VALUE or Int.MIN_VALUE.

We will not dive into these cases today, but it is useful to know that toInt() is not just “cut off the fraction,” but also “fit into the Int range.”

Where exactly toInt() differs from floor/ceil/round

If you take a positive number 7.9, then:

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

And it is easy to make the mistake of deciding that it is the same thing.

Now let’s take the negative value -7.9:

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

The difference is fundamental: floor goes toward minus infinity, while toInt() goes toward zero.

Table to reinforce the idea

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

Here, floor/ceil follow the mathematical definitions, round rounds to the nearest with “halves to even,” and toInt() performs conversion by discarding the fractional part toward zero.

4. Practical rounding techniques

How to get an Int by the rule you need

Often you specifically need an Int: for example, the number of items, the number of pages, the number of packages. But you want to round by the floor/ceil/round rule, not “toward zero.”

Then use a simple technique: first round as a Double, then convert to Int. Yes, in two steps. Yes, that is normal. Kotlin will not be offended.

Example: round up and get an Int

import kotlin.math.ceil

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

    println(pieces) // 4
}

Example: round down and get an Int

import kotlin.math.floor

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

    println(boxes) // 9
}

Example: round to the nearest and get an Int

import kotlin.math.round

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

    println(rounded) // 5
}

Rounding to N digits after the decimal point

Very often you get a task: round to 2 digits (prices, percentages). But round(x) rounds to an integer. What should you do? Use a school trick that in practice shows up all the time: multiply by 10N, round to an integer, then divide back.

Important to remember: we are still in the Double world, and due to precision you may sometimes see a not-perfectly-pretty fraction. But as a baseline technique, it is a great start.

Example: rounding to 2 digits

import kotlin.math.round

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

    println(rounded2) // 12.35
}

The idea is: x * 100 moves the decimal point 2 digits to the right, round makes the number an integer, and / 100 moves the decimal point back.

Mini scenario: a delivery price calculator

Now we will write a small (but good) project that ties everything together. Imagine we have a simple console program that computes the order cost, tax, and shipping. We are not reading input from the console, so we use constants in code — the important part for us is rounding.

Suppose:

  • item price: subtotal
  • tax is 7.5%: taxRate
  • shipping is charged “for each started kilogram” (that is, rounding up)
  • we print the total with 2 digits after the decimal point

Step 1: compute tax and round the total for display

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("Total: $totalPretty") // Total: 42.89
}

An important point here: we rounded only for display. In real tasks, the rules can be stricter (when and how to round), but for today it is enough to understand the mechanics.

Step 2: compute shipping by the rule “for each started kilogram”

Now let’s add weight and price per kilogram. If the weight is 2.1, you must pay as if it were 3 (rounding up).

import kotlin.math.ceil

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

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

    println("Billed weight: $billedKg kg") // Billed weight: 3 kg
    println("Shipping: $shipping")        // Shipping: 9.0
}

Notice: ceil(weightKg) returns a Double, so toInt() is a separate step.

Step 3: why you cannot write weightKg.toInt() instead of ceil(weightKg)

And here is the moment of truth. With positive weights, everything may look “almost fine,” but the meaning will be wrong: 2.1.toInt() gives 2, meaning you will undercharge for shipping.

fun main() {
    val weightKg = 2.1

    println(weightKg.toInt()) // 2  (this is NOT "for each started", this is "truncate toward zero")
}

toInt() is not “rounding up,” and it is not even “mathematical rounding down.” It is conversion with discarding the fractional part toward zero.

Decision scheme for rounding

Next time you choose between floor, ceil, round, and toInt(), it is useful not to shuffle through “I think this is the one,” but to ask yourself one question: what is the rule of the task?

flowchart TD
    A["Have Double x"] --> B{"Is this for displaying to a human?"}
    B -->|Yes| C["Round to N digits: round(x*10^N)/10^N"]
    B -->|No| D{"Do you need a whole number of units?"}
    D -->|Yes, to make sure it's ENOUGH| E["ceil(x) -> toInt()"]
    D -->|Yes, how many fit for sure| F["floor(x) -> toInt()"]
    D -->|No, just convert the type| G["x.toInt() (truncates toward zero)"]

This scheme intentionally reminds you: toInt() is not “universal rounding,” but a separate semantic step.

5. Common mistakes when rounding and converting to Int

Mistake #1: assuming that toInt() is rounding.
In the beginning it is very tempting to think: “converted to Int — so it’s rounded.” In reality, Double.toInt() discards the fractional part toward zero. Because of this, “round down” logic breaks for negative values, and “round up” logic breaks basically always.

Mistake #2: confusing “rounding down” with “toward zero.”
floor(x) rounds down along the number line, i.e., toward negative infinity. But toInt() and truncate(x) (if you run into it) work toward zero. For positive numbers the difference is barely visible, so the mistake hides and then “pops up” later at the most inconvenient moment.

Mistake #3: expecting round(2.5) to produce 3.0.
In Kotlin, round(x) uses “ties to even” — halves are rounded to the nearest even. If you write an algorithm where behavior on .5 matters (for example, points, ratings), make sure to check what is actually expected by the task rules.

Mistake #4: forgetting that ceil/floor/round return Double, and trying to “feed” it into something that expects an Int.
In Kotlin, this usually will not compile (and that is good). But beginners sometimes try to “force” the idea: since it is 3.0, it must be an integer. No: the type is still Double. The correct path is: first rounding (ceil/floor/round), then explicit conversion (toInt()).

Mistake #5: rounding “to 2 digits” without scaling, or with the wrong scale.
The trick round(x * 100.0) / 100.0 works precisely because 100 corresponds to two digits. If you accidentally multiply by 10 but divide by 100, you will get some weird “magic” (and it will honestly be your magic, not Kotlin’s). Treat the scaling coefficient as part of the rule, not as a random number pulled out of thin air.

1
Task
Kotlin SELF, level 6, lesson 3
Locked
Three rounding methods
Three rounding methods
1
Task
Kotlin SELF, level 6, lesson 3
Locked
Rounding Debate
Rounding Debate
1
Task
Kotlin SELF, level 6, lesson 3
Locked
Warehouse packs
Warehouse packs
1
Task
Kotlin SELF, level 6, lesson 3
Locked
Receipt total
Receipt total
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION