Numbers
Integer types
Kotlin provides a set of built-in types that represent numbers.
For integer numbers, there are four types with different sizes and value ranges:
Type | Size (bits) | Min value | Max value |
|---|---|---|---|
| 8 | -128 | 127 |
| 16 | -32768 | 32767 |
| 32 | -2,147,483,648 (-231) | 2,147,483,647 (231 - 1) |
| 64 | -9,223,372,036,854,775,808 (-263) | 9,223,372,036,854,775,807 (263 - 1) |
When you initialize a variable with no explicit type specification, the compiler automatically infers the type with the smallest range enough to represent the value starting from Int. If it doesn't exceed the range of Int, the type is Int. If it does exceed that range, the type is Long. To specify the Long value explicitly, append the suffix L to the value. To use the Byte or Short type, specify it explicitly in the declaration. Explicit type specification triggers the compiler to check that the value doesn't exceed the range of the specified type.
Floating-point types
For real numbers, Kotlin provides floating-point types Float and Double that adhere to the IEEE 754 standard. Float reflects the IEEE 754 single precision, while Double reflects double precision.
These types differ in their size and provide storage for floating-point numbers with different precision:
Type | Size (bits) | Significant bits | Exponent bits | Decimal digits |
|---|---|---|---|---|
| 32 | 24 | 8 | 6-7 |
| 64 | 53 | 11 | 15-16 |
You can initialize Double and Float variables only with numbers that have a fractional part. Separate the fractional part from the integer part by a period (.)
For variables initialized with fractional numbers, the compiler infers the Double type:
To explicitly specify the Float type for a value, add the suffix f or F. If a value provided in this way contains more than 7 decimal digits, it is rounded:
Unlike in some other languages, there are no implicit widening conversions for numbers in Kotlin. For example, a function with a Double parameter can be called only on Double values, but not Float, Int, or other numeric values:
To convert numeric values to different types, use explicit conversions.
Literal constants for numbers
There are several kinds of literal constants for integral values:
Decimals:
123Longs, ending with the capital
L:123LHexadecimals:
0x0FBinaries:
0b00001011
Kotlin also supports conventional notation for floating-point numbers:
Doubles (default when the fractional part does not end with a letter):
123.5,123.5e10Floats, ending with the letter
forF:123.5f
You can use underscores to make number constants more readable:
Boxing and caching numbers on the Java Virtual Machine
The way the JVM stores numbers can make your code behave counterintuitively because of the cache used by default for small (byte-sized) numbers.
The JVM stores numbers as primitive types: int, double, and so on. When you use generic types or create a nullable number reference such as Int?, numbers are boxed in Java classes such as Integer or Double.
The JVM applies a memory optimization technique to Integer and other objects that represent numbers between −128 and 127. All nullable references to such objects refer to the same cached object. For example, nullable objects in the following code are referentially equal:
For numbers outside this range, the nullable objects are different but structurally equal:
For this reason, Kotlin warns about using referential equality with boxable numbers and literals with the following message: "Identity equality for arguments of types ... and ... is prohibited." When comparing Int, Short, Long, and Byte types (as well as Char and Boolean), use structural equality checks to get consistent results.
Explicit number conversions
Due to different representations, number types are not subtypes of each other. As a consequence, smaller types are not implicitly converted to bigger types and vice versa. For example, assigning a value of type Byte to an Int variable requires an explicit conversion:
All number types support conversions to other types:
toShort(): ShorttoInt(): InttoLong(): LongtoFloat(): FloattoDouble(): Double
In many cases, there is no need for explicit conversion because the type is inferred from the context, and arithmetical operators are overloaded to handle conversions automatically. For example:
Reasoning against implicit conversions
Kotlin doesn't support implicit conversions because they can lead to unexpected behavior.
If numbers of different types were converted implicitly, we could sometimes lose equality and identity silently. For example, imagine if Int was a subtype of Long:
Operations on numbers
Kotlin supports the standard set of arithmetical operations over numbers: +, -, *, /, %. They are declared as members of appropriate classes:
You can override these operators in custom number classes. See Operator overloading for details.
Division of integers
Division between integer numbers always returns an integer number. Any fractional part is discarded.
This is true for a division between any two integer types:
To return a division result with the fractional part, explicitly convert one of the arguments to a floating-point type:
Bitwise operations
Kotlin provides a set of bitwise operations on integer numbers. They operate on the binary level directly with bits of the numbers' representation. Bitwise operations are represented by functions that can be called in infix form. They can be applied only to Int and Long:
The complete list of bitwise operations:
shl(bits)– signed shift leftshr(bits)– signed shift rightushr(bits)– unsigned shift rightand(bits)– bitwise ANDor(bits)– bitwise ORxor(bits)– bitwise XORinv()– bitwise inversion
Floating-point numbers comparison
The operations on floating-point numbers discussed in this section are:
Equality checks:
a == banda != bComparison operators:
a < b,a > b,a <= b,a >= bRange instantiation and range checks:
a..b,x in a..b,x !in a..b
When the operands a and b are statically known to be Float or Double or their nullable counterparts (the type is declared or inferred or is a result of a smart cast), the operations on the numbers and the range that they form follow the IEEE 754 Standard for Floating-Point Arithmetic.
However, to support generic use cases and provide total ordering, the behavior is different for operands that are not statically typed as floating-point numbers. For example, Any, Comparable<...>, or Collection<T> types. In this case, the operations use the equals and compareTo implementations for Float and Double. As a result:
NaNis considered equal to itselfNaNis considered greater than any other element includingPOSITIVE_INFINITY-0.0is considered less than0.0
Here is an example that shows the difference in behavior between operands statically typed as floating-point numbers (Double.NaN) and operands not statically typed as floating-point numbers (listOf(T)).