1. The Double and Float types and their literals
When you’re just starting to program, it feels like Int is enough for everything: add a sum, subtract, multiply, compare. But as soon as a fractional part shows up in a problem (percentages, averages, measurements, speed, temperature, “half” of something), Int starts behaving like a very strict accountant: “fractions do not exist, here’s an integer — and don’t argue”.
Real numbers are needed where the fractional part is not decoration but the point. For example, “average speed 37.5 km/h”, “discount 12.5%”, “temperature 36.6”, “density 1.225” — all of this is poorly expressed with integers. In theory, you can store “36.6” as “366” and remember that it’s tenths, but that’s an agreement that’s easy to forget. We’ll even use this as a learning technique (for example, storing money in cents), but only consciously.
In Kotlin (as in many languages), there are dedicated types for fractional calculations. Today we care about two: Double and Float. They are similar in meaning (“numbers with a fractional part”), but they differ in precision and in how often you’ll choose them in everyday tasks.
Double — the default choice for fractions
Double is the main workhorse for real numbers in Kotlin. It’s often used “by default” when you need a fractional part and don’t have specific constraints. In practice, you’ll see Double all the time: in formulas, calculations, geometry, percentages, and almost everywhere you get “non-integer” results.
Double has a very convenient cue: if you write a number with a decimal point, Kotlin usually treats it as Double. For example, in the Kotlin documentation you can see an example: val PI = 3.14 — the type of such a value is inferred automatically.
Let’s lock in the core idea: a literal with a decimal point (10.0, 2.5, 3.14) is a Double.
Mini example — let’s just look at the calculations and output:
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
}
Note: I intentionally wrote radius = 2.0, not 2. That’s not “for looks”; it’s so we immediately work in the world of fractional numbers and don’t get surprises during division (we’ll get to that in the next section).
Float — also a fractional type, but its literal requires the f suffix
Float is also a real number, just usually less precise and more compact. For a beginner, it’s important to understand not so much “how many bits”, but two practical facts: first, Float is less common in beginner exercises, and second, Kotlin won’t let you accidentally write a Float literal without an explicit marker.
Main idea: if you want a literal of type Float, you must add the f suffix. For example, 2.5f. This isn’t Kotlin being picky; it’s a way to force you to be explicit: “I really want Float, I’ve thought about it, I agree”.
This style appears in Kotlin documentation examples too; for instance, -3.4f is specifically a Float.
Mini example:
fun main() {
val a: Float = 2.5f
val b: Double = 2.5
println("a = $a") // a = 2.5
println("b = $b") // b = 2.5
}
And one more important thing you’ll almost certainly hit in your first few days: if you write val x: Float = 2.5 without the f, the compiler will complain. It sees 2.5 as a Double, and Kotlin doesn’t like “silent” numeric conversions. That’s good: less magic means fewer unexpected bugs.
For now, remember a simple rule: if you’re not sure you need Float, use Double. Float is more often used in tasks like graphics or large arrays of numbers where saving memory matters, but in our first programs this usually isn’t critical.
Literals: 10 and 10.0 are different types
Now comes a moment where the brain likes to go “aha, got it” — and then immediately make a mistake on the next line. In Kotlin, two visually similar numbers can be different types simply because one has a decimal point and the other doesn’t. 10 is an integer (usually Int), while 10.0 is already fractional (Double).
This matters because the type affects how operations on numbers work. Especially division. Especially when you expect a fractional result but get an integer and think: “Who stole my 0.5?”
Mini example:
fun main() {
val a = 10 // Int (integer)
val b = 10.0 // Double (real)
println("a = $a") // a = 10
println("b = $b") // b = 10.0
}
Yes, even the output hints at it: 10.0 looks like a number with a fractional part (even if the fraction is zero). That’s useful: visually, you can see you’re working in a different “mode”.
From this comes a simple (and very practical) trick: if you want to “move calculations into the world of fractions”, it’s enough to put .0 somewhere in the expression. It sounds too simple, but that’s exactly what people often do in basic tasks before they move on to other type conversions.
3. Division: integers vs real numbers
Division is the #1 source of surprise for beginners. And it’s an honest surprise: “But I divided in school — why is it different here?” The answer is simple: the computer performs division depending on the types. If you divide an integer by an integer, the result is also an integer. The fractional part is simply discarded.
And this behavior is not a “Kotlin bug”; it’s standard integer arithmetic in programming: Int / Int -> Int. To get a fractional result, at least one operand must be Double or Float.
Mini example worth memorizing like the password to the door “into the world of fractions”:
fun main() {
val a = 5 / 2
val b = 5.0 / 2
println("a = $a") // a = 2
println("b = $b") // b = 2.5
}
Now let’s try a bit of business logic. Let’s calculate a receipt total, but to avoid dealing with input of real numbers (that’s a separate topic), we’ll store money in cents as Int, and for output convert to euros by dividing by 100.0.
Note: divide by 100.0, not by 100. This is essential.
fun main() {
val totalCents = 2599 // 25.99 (some euros/dollars — doesn’t matter)
val total = totalCents / 100.0 // Double: 25.99
println("totalCents = $totalCents") // totalCents = 2599
println("total = $total") // total = 25.99
}
If we wrote totalCents / 100, we’d get 25, and you would lose 99 cents. The program would happily “save” the user money, but unfortunately only in the console.
To reinforce this, let’s add one more typical operation: split the bill among several people. Let the number of people be entered as an integer — that part is familiar.
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
}
Yes, the output is “long”. That’s normal: we’re not rounding or formatting nicely yet. Our goal right now is to understand why we got a non-integer number at all and how to obtain a fractional result. Pretty output comes later; today we’re “learning to walk”, not “dancing”.
A small reminder diagram for how to think about division:
flowchart TD
A["You write division x / y"] --> B{"Are x and y integers (Int)?"}
B -- "yes" --> C["The result is also Int; the fractional part is lost"]
B -- "no (Double/Float is present)" --> D["The result is real; the fractional part is preserved"]
If you’re unsure, just ask yourself: “Does this expression contain at least one number with a decimal point?” If not, there’s a high chance of integer behavior.
4. Scientific notation: 1e3 and 1e-3
When numbers become very large or very small, writing them “head-on” is inconvenient. No one wants to read 0.000001 and count zeros with their eyes, especially when the eyes are already tired after the tenth println. For such cases, there is scientific notation (also called exponential): 1e3 means “1 multiplied by 10 to the power of 3”.
In Kotlin, such literals are also real numbers (usually Double). This is convenient: you can write coefficients, tolerances, scales, and other “small/large” values in a short and readable way.
Mini example:
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
}
Why is this useful specifically for you as beginners? Because scaling factors often appear in formulas. For example, “convert millimeters to meters” means multiply by 1e-3. “convert kilograms to grams” means multiply by 1e3. And this notation is much less likely to cause the “put a zero in the wrong place” mistake.
At the same time, don’t be scared if Kotlin sometimes prints the number back as 1.0E-3 or something similar. That’s just a display format. The number is the same; it’s simply printed more compactly.
5. What to choose: Double or Float
You want a clear rule so you don’t suffer? Good news: at the level of our tasks, it’s truly simple. If you’re writing a typical console program, calculating percentages or averages, converting units, computing the area of a circle, or splitting a bill with friends, choose Double. It’s the most familiar and “wide” option.
You should use Float when you have a specific reason. Most often, the reason sounds like “we have a lot of numbers and we’re saving memory” or “we’re working in an environment where using Float is common (for example, some graphics calculations)”. But those stories usually start later, when you already write code confidently and understand exactly where you need it.
If you want to anchor this more reliably in your head, you can use a simple table (not as an exam cheat sheet, but as a reminder of “what am I doing at all”):
| Type | What the literal looks like | Example | When you need it at the beginning |
|---|---|---|---|
|
no decimal point | |
counters, quantities, indices |
|
with a decimal point / e | |
fractional calculations “by default” |
|
with a decimal point + f | |
rarely; only if there’s a reason |
And the last important rule you can treat as “ironclad” today: if you need a fractional result, make sure the expression is evaluated as Double (or Float). The simplest way is to use at least one literal with a decimal point: 100.0, 5.0, 2.0. It sounds almost like a lifehack, but early on it saves you from a huge number of mistakes.
6. Typical mistakes
Mistake #1: expecting 5 / 2 to produce 2.5.
This is a classic. The problem is that both numbers are integers, which means the result is also an integer. The fix is simple: make at least one number real, for example 5.0 / 2 or 5 / 2.0. A useful habit: in tasks with averages and percentages, write the “base” numbers as Double (with a decimal point) right away so you don’t forget.
Mistake #2: trying to assign a Float without the f suffix.
val x: Float = 2.5 looks logical in human terms, but Kotlin sees 2.5 as a Double and doesn’t want to “silently” convert one into the other. The correct form is: 2.5f. If you’re not sure you really need Float, it’s better not to touch it and use Double instead — fewer reasons to argue with the compiler.
Mistake #3: dividing money/percentages as integers and “losing the tail”.
When you store money in cents (Int) and convert to Swiss francs, it’s important to divide by 100.0, not by 100. Division by 100 will produce an integer and discard the remainder. This mistake is especially tricky because the program runs “fine”; the result just becomes wrong, and the wrongness is not always obvious right away.
Mistake #4: being scared of scientific notation and treating it like a “different type”.
1e-3 is not some special secret type; it’s a normal real-number literal. It means “multiply by 10 to the power of -3”. If you see an output format like 1.0E-3, that’s also normal: Kotlin (more precisely, standard number printing) sometimes prints small values in exponential form to avoid drawing lots of zeros.
Mistake #5: choosing Float “just because it’s shorter”.
Sometimes it seems like Float is a “lighter Double”, so it must be better. In practice, for learning tasks this more often creates extra hassle: literals require f, type errors happen more often, and you don’t feel any benefit. So for now, keep the calm rule: Double by default; Float only when you consciously need it.
GO TO FULL VERSION