Number Types
Different types of numbers are stored in different ways.
If you create an identifier and assign an integer value to it, Kotlin infers
the Int type:
// NumberTypes/InferInt.kt
fun main() {
val million = 1_000_000 // Infers Int
println(million)
}
/* Output:
1000000
*/
For readability, Kotlin allows underscores within numerical values.
The basic mathematical operators for numbers are the ones available in most
programming languages: addition (+), subtraction (-), division (/),
multiplication (*) and modulus (%), which produces the remainder from
integer division:
// NumberTypes/Modulus.kt
fun main() {
val numerator: Int = 19
val denominator: Int = 10
println(numerator % denominator)
}
/* Output:
9
*/
Integer division truncates its result:
// NumberTypes/IntDivisionTruncates.kt
fun main() {
val numerator: Int = 19
val denominator: Int = 10
println(numerator / denominator)
}
/* Output:
1
*/
If the operation had rounded the result, the output would be 2.
The order of operations follows basic arithmetic:
// NumberTypes/OpOrder.kt
fun main() {
println(45 + 5 * 6)
}
/* Output:
75
*/
The multiplication operation 5 * 6 is performed first, followed by the
addition 45 + 30.
If you want 45 + 5 to happen first, use parentheses:
// NumberTypes/OpOrderParens.kt
fun main() {
println((45 + 5) * 6)
}
/* Output:
300
*/
Now let’s calculate body mass index (BMI), which is weight in kilograms
divided by the square of the height in meters. If you have a BMI of less than
18.5, you are underweight. Between 18.5 and 24.9 is normal weight. BMI of
25 and higher is overweight. This example also shows the preferred formatting
style when you can’t fit the function’s parameters on a single line:
// NumberTypes/BMIMetric.kt
fun bmiMetric(
weight: Double,
height: Double
): String {
val bmi = weight / (height * height) // [1]
return if (bmi < 18.5) "Underweight"
else if (bmi < 25) "Normal weight"
else "Overweight"
}
fun main() {
val weight = 72.57 // 160 lbs
val height = 1.727 // 68 inches
val status = bmiMetric(weight, height)
println(status)
}
/* Output:
Normal weight
*/
-
[1] If you remove the parentheses, you divide
weightbyheightthen multiply that result byheight. That’s a much larger number, and the wrong answer.
bmiMetric() uses Doubles for the weight and height. A Double holds very
large and very small floating-point numbers.
Here’s a version using English units, represented by Int parameters:
// NumberTypes/BMIEnglish.kt
fun bmiEnglish(
weight: Int,
height: Int
): String {
val bmi =
weight / (height * height) * 703.07 // [1]
return if (bmi < 18.5) "Underweight"
else if (bmi < 25) "Normal weight"
else "Overweight"
}
fun main() {
val weight = 160
val height = 68
val status = bmiEnglish(weight, height)
println(status)
}
/* Output:
Underweight
*/
Why does the result differ from bmiMetric(), which uses Doubles? When you
divide an integer by another integer, Kotlin produces an integer result. The
standard way to deal with the remainder during integer division is
truncation, meaning “chop it off and throw it away” (there’s no rounding). So
if you divide 5 by 2 you get 2, and 7/10 is zero. When Kotlin
calculates bmi in expression [1], it divides 160 by 68 * 68 and gets
zero. It then multiplies zero by 703.07 to get zero.
To avoid this problem, move 703.07 to the front of the calculation. The
calculations are then forced to be Double:
val bmi = 703.07 * weight / (height * height)
The Double parameters in bmiMetric() prevent this problem. Convert
computations to the desired type as early as possible to preserve accuracy.
All programming languages have limits to what they can store within an integer.
Kotlin’s Int type can take values between -231 and +231-1, a constraint
of the Int 32-bit representation. If you sum or multiply two Ints that are
big enough, you’ll overflow the result:
// NumberTypes/IntegerOverflow.kt
fun main() {
val i: Int = Int.MAX_VALUE
println(i + i)
}
/* Output:
-2
*/
Int.MAX_VALUE is a predefined value which is the largest number an Int can
hold.
The overflow produces a result that is clearly incorrect, as it is both negative and much smaller than we expect. Kotlin issues a warning whenever it detects a potential overflow.
Preventing overflow is your responsibility as a developer. Kotlin can’t always detect overflow during compilation, and it doesn’t prevent overflow because that would produce an unacceptable performance impact.
If your program contains large numbers, you can use Longs, which accommodate
values from -263 to +263-1. To define a val
of type Long, you can specify the type explicitly or put L at the end of a
numeric literal, which tells Kotlin to treat that value as a Long:
// NumberTypes/LongConstants.kt
fun main() {
val i = 0 // Infers Int
val l1 = 0L // L creates Long
val l2: Long = 0 // Explicit type
println("$l1 $l2")
}
/* Output:
0 0
*/
By changing to Longs we prevent the overflow in IntegerOverflow.kt:
// NumberTypes/UsingLongs.kt
fun main() {
val i = Int.MAX_VALUE
println(0L + i + i) // [1]
println(1_000_000 * 1_000_000L) // [2]
}
/* Output:
4294967294
1000000000000
*/
Using a numeric literal in both [1] and [2] forces Long calculations,
and also produces a result of type Long. The location where the L appears
is unimportant. If one of the values is Long, the resulting expression is
Long.
Although they can hold much larger values than Ints, Longs still have size
limitations:
// NumberTypes/BiggestLong.kt
fun main() {
println(Long.MAX_VALUE)
}
/* Output:
9223372036854775807
*/
Long.MAX_VALUE is the largest value a Long can hold.
Exercises and solutions can be found at www.AtomicKotlin.com.