Number Types

Different types of numbers are stored in different ways.

If you define an identifier and assign an integer value to it, Kotlin infers the Int type:

// NumberTypes/InferInt.kt fun main(args: Array<String>) { val million = 1_000_000 // Infers Int println(million) } /* Output: 1000000 */

Kotlin allows underscores in numerical values, to make numbers more readable.

The basic mathematical operators for numbers are the same as 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(args: Array<String>) { val numerator: Int = 19 val denominator: Int = 10 println(numerator % denominator) } /* Output: 9 */

Integer division truncates, rather than rounds, the result:

// NumberTypes/IntDivisionTruncates.kt fun main(args: Array<String>) { 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.

Programming languages define the order in which operations are performed. The order of arithmetic operations is straightforward:

// NumberTypes/OpOrder.kt fun main(args: Array<String>) { 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(args: Array<String>) { println( (45 + 5) * 6 ) } /* Output: 300 */

As another example, let’s calculate body mass index (BMI), which is weight in kilograms divided by height in meters squared. 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. Here, we see the preferred formatting style when you can’t fit all function arguments on a single line:

// NumberTypes/BMI.kt fun getBmiStatus( kg: Double, heightM: Double ): String { val bmi = kg / (heightM * heightM) // [1] return if (bmi < 18.5) "Underweight" else if (bmi < 25) "Normal weight" else "Overweight" } fun main(args: Array<String>) { val kg = 72.57 // 160 lbs val heightM = 1.727 // 68 inches val status = getBmiStatus(kg, heightM) println(status) } /* Output: Normal weight */

BMI.kt uses Doubles for the weight and height. A Double holds very large and very small floating-point numbers. Here’s a version using Ints (for English units instead of metric):

// NumberTypes/IntegerMath.kt fun getBmiStatusInt( lbs: Int, height: Int ): String { val bmi = lbs / (height * height) * 703.07 // [1] return if (bmi < 18.5) "Underweight" else if (bmi < 25) "Normal weight" else "Overweight" } fun main(args: Array<String>) { val lbs = 160 val height = 68 val status = getBmiStatusInt(lbs, height) println(status) } /* Output: Underweight */

Why does the result differ from the previous version that 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, first multiply by 703.07 to force the result type of the computation to be Double:

val bmi = 703.07 * lbs / (height * height)

Note how using Double arguments in BMI.kt’s version of getBmiStatus() prevents this problem. Try to convert computations to the desired type as early as possible, otherwise you might lose accuracy.

All programming languages have limits to what they can store within an integer. Kotlin’s Int type is restricted to the values between -231 and +231-1. (That constraint comes from the 32-bit representation for Int values). If you sum or multiply two Ints that are big enough, you’ll overflow the result:

// NumberTypes/IntegerOverflow.kt fun main(args: Array<String>) { val i: Int = Int.MAX_VALUE println(i + i) } /* Output: -2 */

Int.MAX_VALUE is the special predefined value which is the largest number an Int can hold.

Not only do you get an output that is clearly not large—this is the effect of the overflow—but you also get a warning during compilation.

To achieve better performance for numerical operations, Kotlin doesn’t prevent integer overflow, and it can’t always detect overflow during compilation. Preventing overflow is your responsibility as a developer.

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 the compiler to treat that value as a Long:

// NumberTypes/LongConstants.kt fun main(args: Array<String>) { 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 because Longs hold much larger values:

// NumberTypes/UsingLongs.kt fun main(args: Array<String>) { 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(args: Array<String>) { println(Long.MAX_VALUE) } /* Output: 9223372036854775807 */
Long.MAX_VALUE is the special predefined value which is the largest number a Long can hold.

Previous          Next

©2018 Mindview LLC. All Rights Reserved.