Skip to content

数字

整数类型

Kotlin 提供了一组内置类型来表示数字。 对于整数,有四种不同大小和值区间的类型:

类型大小 (位)最小值最大值
Byte8-128127
Short16-3276832767
Int32-2,147,483,648 (-231)2,147,483,647 (231 - 1)
Long64-9,223,372,036,854,775,808 (-263)9,223,372,036,854,775,807 (263 - 1)

除了有符号整数类型之外,Kotlin 还提供了无符号整数类型。 由于无符号整数面向一组不同的用例,因此它们将在单独的部分中介绍。 关于无符号整数类型,请参见 unsigned-integer-types.md

当你初始化一个变量时,如果没有显式类型指定,编译器会自动推断出从 Int 开始足以表示该值的最小区间类型。如果该值未超出 Int 的区间,则类型为 Int。如果超出该区间,则类型为 Long。要显式指定 Long 值,请在值后面附加后缀 L。要使用 ByteShort 类型,请在声明中显式指定。显式类型指定会触发编译器检测值是否超出了指定类型的区间。

kotlin
val one = 1 // Int
val threeBillion = 3000000000 // Long
val oneLong = 1L // Long
val oneByte: Byte = 1

浮点类型

对于实数,Kotlin 提供了浮点类型 FloatDouble,它们符合 IEEE 754 标准Float 反映了 IEEE 754 的 单精度,而 Double 则反映了 双精度

这些类型在大小上有所不同,并为不同精度的浮点数提供存储:

类型大小 (位)有效位指数位十进制位数
Float322486-7
Double64531115-16

你只能用带小数部分的数字来初始化 DoubleFloat 变量。用句点(.)将小数部分与整数部分分隔开。

对于用小数初始化的变量,编译器会推断为 Double 类型:

kotlin
val pi = 3.14          // Double

val one: Double = 1    // Int is inferred
// Initializer type mismatch

val oneDouble = 1.0    // Double

要为值显式指定 Float 类型,请添加后缀 fF。如果以这种方式提供的值包含超过 7 位小数,则会被四舍五入:

kotlin
val e = 2.7182818284          // Double
val eFloat = 2.7182818284f    // Float, actual value is 2.7182817

与某些其他语言不同,Kotlin 中没有数字的隐式加宽转换。例如,带有 Double 形参的函数只能对 Double 值调用,而不能对 FloatInt 或其他数值类型调用:

kotlin
fun main() {
    fun printDouble(x: Double) { print(x) }

    val x = 1.0
    val xInt = 1    
    val xFloat = 1.0f 

    printDouble(x)
    
    printDouble(xInt)   
    // Argument type mismatch
    
    printDouble(xFloat)
    // Argument type mismatch
}

要将数值转换为不同类型,请使用显式数字转换

数字字面值常量

整数值有几种字面值常量:

  • 十进制:123
  • 长整型,以大写 L 结尾:123L
  • 十六进制:0x0F
  • 二进制:0b00001011

Kotlin 不支持八进制字面值。

Kotlin 还支持浮点数的常规表示法:

  • 双精度浮点型(当小数部分不以字母结尾时的默认值):123.5123.5e10
  • 单精度浮点型,以字母 fF 结尾:123.5f

你可以使用下划线使数字常量更具可读性:

kotlin
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
val bigFractional = 1_234_567.7182818284

无符号整数字面值也有特殊的后缀。 关于无符号整数类型的字面值的更多信息,请参阅该文档。

Java 虚拟机上的数字装箱和缓存

JVM 存储数字的方式可能会让你的代码行为反直觉,因为 JVM 默认会对小(字节大小)数字使用缓存。

JVM 将数字存储为原生类型:intdouble 等。当你使用泛型或创建可空的数字引用(例如 Int?)时,数字会被装箱到 Java 类中,例如 IntegerDouble

JVM 对表示 −128127 之间数字的 Integer 及其他对象应用了内存优化技术。所有对此类对象的可空引用都指向相同的缓存对象。例如,以下代码中的可空对象是引用相等的

kotlin
fun main() {
    val a: Int = 100
    val boxedA: Int? = a
    val anotherBoxedA: Int? = a
    
    println(boxedA === anotherBoxedA) // true
}

对于超出此区间的数字,可空对象是不同的,但却是结构相等的

kotlin
fun main() {
    val b: Int = 10000
    val boxedB: Int? = b
    val anotherBoxedB: Int? = b
    
    println(boxedB === anotherBoxedB) // false
    println(boxedB == anotherBoxedB) // true
}

因此,Kotlin 会在使用可装箱数字和字面值进行引用相等性比较时发出警告,并显示以下消息:“Identity equality for arguments of types ... and ... is prohibited.”在比较 IntShortLongByte 类型(以及 CharBoolean)时,请使用结构相等性检测来获得一致的结果。

显式数字转换

由于表示方式不同,数字类型_不是彼此的子类型_。因此,较小类型_不会_隐式转换为较大类型,反之亦然。例如,将 Byte 类型的值赋值给 Int 变量需要显式转换:

kotlin
fun main() {
    val byte: Byte = 1
    // OK, literals are checked statically
    
    val intAssignedByte: Int = byte 
    // Initializer type mismatch
    
    val intConvertedByte: Int = byte.toInt()
    
    println(intConvertedByte)
}

所有数字类型都支持转换为其他类型:

  • toByte(): Byte (对于 FloatDouble 已弃用)
  • toShort(): Short
  • toInt(): Int
  • toLong(): Long
  • toFloat(): Float
  • toDouble(): Double

在许多情况下,不需要显式转换,因为类型是从上下文中推断出来的,并且算术操作符已重载以自动处理转换。例如:

kotlin
fun main() {
    val l = 1L + 3       // Long + Int => Long
    println(l is Long)   // true
}

反对隐式转换的理由

Kotlin 不支持隐式转换,因为它们可能导致意外行为。

如果不同类型的数字被隐式转换,我们有时可能会悄无声息地失去相等性和标识。例如,设想如果 IntLong 的子类型:

kotlin
// Hypothetical code, does not actually compile:
val a: Int? = 1    // A boxed Int (java.lang.Integer)
val b: Long? = a   // Implicit conversion yields a boxed Long (java.lang.Long)
print(b == a)      // Prints "false" as Long.equals() checks not only the value but whether the other number is Long as well

数字上的操作

Kotlin 支持对数字进行标准算术操作:+-*/%。它们被声明为相应类的成员:

kotlin
fun main() {
    println(1 + 2)
    println(2_500_000_000L - 1L)
    println(3.14 * 2.71)
    println(10.0 / 3)
}

你可以在自定义数字类中覆盖这些操作符。关于详细信息,请参见操作符重载

整数除法

整数之间的除法总是返回一个整数。任何小数部分都会被丢弃。

kotlin
fun main() {
    val x = 5 / 2
    println(x == 2.5) 
    // Operator '==' cannot be applied to 'Int' and 'Double'
    
    println(x == 2)   
    // true
}

这对于任意两个整数类型之间的除法都适用:

kotlin
fun main() {
    val x = 5L / 2
    println (x == 2)
    // Error, as Long (x) cannot be compared to Int (2)
    
    println(x == 2L)
    // true
}

要返回带有小数部分的除法结果,请将其中一个实参显式转换为浮点类型:

kotlin
fun main() {
    val x = 5 / 2.toDouble()
    println(x == 2.5)
}

位操作

Kotlin 提供了一组整数上的_位操作_。它们直接在二进制级别上操作数字表示的位。位操作通过可以中缀形式调用的函数来表示。它们只能应用于 IntLong

kotlin
fun main() {
    val x = 1
    val xShiftedLeft = (x shl 2)
    println(xShiftedLeft)  
    // 4
    
    val xAnd = x and 0x000FF000
    println(xAnd)          
    // 0
}

完整的位操作列表:

  • shl(bits) – 有符号左移
  • shr(bits) – 有符号右移
  • ushr(bits) – 无符号右移
  • and(bits) – 按位
  • or(bits) – 按位
  • xor(bits) – 按位 异或
  • inv() – 按位反转

浮点数比较

本节讨论的浮点数操作有:

  • 相等性检测:a == ba != b
  • 比较操作符:a < ba > ba <= ba >= b
  • 区间实例化和区间检测:a..bx in a..bx !in a..b

当操作数 ab 被静态地知晓为 FloatDouble 或它们的可空对应物(类型已声明或推断,或为智能转换的结果)时,对数字的操作以及它们形成的区间遵循 IEEE 754 浮点运算标准

然而,为了支持泛型用例并提供全序,对于被静态类型化为浮点数的操作数,行为会有所不同。例如,AnyComparable<...>Collection<T> 类型。在这种情况下,操作会使用 FloatDoubleequalscompareTo 实现。结果是:

  • NaN 被认为是与自身相等的
  • NaN 被认为大于任何其他元素,包括 POSITIVE_INFINITY
  • -0.0 被认为小于 0.0

以下示例显示了静态类型为浮点数的操作数(Double.NaN)与静态类型化为浮点数的操作数(listOf(T))之间的行为差异。

kotlin
fun main() {
    // Operand statically typed as floating-point number
    println(Double.NaN == Double.NaN)                 // false
    
    // Operand NOT statically typed as floating-point number
    // So NaN is equal to itself
    println(listOf(Double.NaN) == listOf(Double.NaN)) // true

    // Operand statically typed as floating-point number
    println(0.0 == -0.0)                              // true
    
    // Operand NOT statically typed as floating-point number
    // So -0.0 is less than 0.0
    println(listOf(0.0) == listOf(-0.0))              // false

    println(listOf(Double.NaN, Double.POSITIVE_INFINITY, 0.0, -0.0).sorted())
    // [-0.0, 0.0, Infinity, NaN]
}