Edit Page

数值类型

最终更新: 2024/03/21

整数类型

Kotlin 提供了一组内建数据类型来表达数值. 对于整数数值, 有 4 种数据类型, 它们的大小不同, 因此表达的数值范围也不同:

类型 大小(bits) 最小值 最大值
Byte 8 -128 127
Short 16 -32768 32767
Int 32 -2,147,483,648 (-231) 2,147,483,647 (231 - 1)
Long 64 -9,223,372,036,854,775,808 (-263) 9,223,372,036,854,775,807 (263 - 1)

如果你初始化一个变量, 不明确指定类型, 编译器会自动推断类型, 使用从 Int 开始、足够表达这个值的最小的整数范围. 如果值没有超过 Int 类型的最大范围, 那么类型会推断为 Int. 如果超过, 那么类型将是 Long. 如果要明确指明一个数值是 Long 类型, 请在数值末尾添加 L 后缀. 如果明确指定类型, 编译器会检查值有没有超过指定类型的最大范围.

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

除整数类型外, Kotlin 还提供了无符号整数类型. 详情请参见 无符号整数类型.

浮点类型

对于实数数值, Kotlin 提供了符合 IEEE 754 标准 的浮点类型 FloatDouble. Float 代表 IEEE 754 单精度(single precision)浮点数, 而 Double 代表 双精度(double precision)浮点数.

这两种类型的区别在于它们大小, 以及能够存储的浮点数值精度:

类型 大小(bits) 有效位数 指数位数 十进制位数
Float 32 24 8 6-7
Double 64 53 11 15-16

可以使用带小数部分的数值初始化 DoubleFloat 变量. 小数部分与整数部分用点号(.)分隔. 任何变量如果使用浮点数值初始化, 编译器推断的类型将是 Double:

val pi = 3.14 // Double 类型
// val one: Double = 1 // 编译错误: 类型不匹配
val oneDouble = 1.0 // Double 类型

如果要明确指明一个数值是 Float 类型, 请在数值末尾添加 fF 后缀. 如果这个值包含 6-7 位以上的十进制数字, 这部分会被舍去:

val e = 2.7182818284 // Double 类型
val eFloat = 2.7182818284f // Float 类型, 实际的值将是 2.7182817

与其他一些语言不同, Kotlin 的数值类型没有隐式的拓宽变换. 比如, 如果函数使用 Double 参数, 那么只能使用 Double 值调用它, 而不能使用 Float, Int, 或其他数值类型的值:

fun main() {
    fun printDouble(d: Double) { print(d) }

    val i = 1    
    val d = 1.0
    val f = 1.0f

    printDouble(d)
//    printDouble(i) // 编译错误: 类型不匹配
//    printDouble(f) // 编译错误: 类型不匹配
}

如果要将数值转换为不同的类型, 请使用 显式类型转换.

数值的字面值常数(Literal Constant)

对于整数值, 有以下几种类型的字面值常数:

  • 10进制数: 123
  • Long 类型需要大写的 L 来标识: 123L
  • 16进制数: 0x0F
  • 2进制数: 0b00001011

Kotlin 不支持8进制数的字面值.

Kotlin 还支持传统的浮点数值表达方式:

  • 无标识时默认为 Double 值: 123.5, 123.5e10
  • Float 值需要用 fF 标识: 123.5f

你可以在数字字面值中使用下划线, 提高可读性:

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

除此之外, 对无符号整数类型的字面值还有特殊的标识.
详情请参见 无符号整数类型的字面值.

数值类型在 JVM 平台的内部表达

在 JVM 平台中, 数值的存储使用基本类型: int, double, 等等. 除非你创建一个可为 null 的数值引用, 比如 Int?, 或使用泛型. 这种情况下数值会被装箱(box)为 Java 类 Integer, Double, 等等.

可为 null 的数值引用即使指向相同的数值, 也可能指向不同的对象:

fun main() {
//sampleStart
    val a: Int = 100
    val boxedA: Int? = a
    val anotherBoxedA: Int? = a

    val b: Int = 10000
    val boxedB: Int? = b
    val anotherBoxedB: Int? = b

    println(boxedA === anotherBoxedA) // 结果为 true
    println(boxedB === anotherBoxedB) // 结果为 false
//sampleEnd
}

所有指向 a 的可为 null 的引用实际上都是同一个对象, 因为JVM 针对 -128127 之间的 Integer 类型会进行内存优化. 但这种优化对 b 的引用无效, 因此这些引用是不同的对象.

但是, 对象仍然是相等的:

fun main() {
//sampleStart
    val b: Int = 10000
    println(b == b) // 打印结果为 'true'
    val boxedB: Int? = b
    val anotherBoxedB: Int? = b
    println(boxedB == anotherBoxedB) // 打印结果为 'true'
//sampleEnd
}

显式数值类型转换

由于数据类型内部表达方式的差异, 较小的数据类型 不是较大数据类型的子类型(subtype). 如果小数据类型是大数据类型的子类型, 那么我们将会遇到以下问题:

// 以下为假想代码, 实际上是无法编译的:
val a: Int? = 1 // 装箱后的 Int (java.lang.Integer)
val b: Long? = a // 这里进行隐式类型转换, 产生一个装箱后的 Long (java.lang.Long)
print(b == a) // 结果与你期望的相反! 这句代码打印的结果将是 "false", 因为 Long 的 equals() 方法会检查比较对象, 要求对方也是一个 Long 对象

这样, 不仅不能保持同一性(identity), 而且还静悄悄地失去了内容相等性(equality).

由于存在以上问题, Kotlin 中较小的数据类型 不会隐式地转换为 较大的数据类型. 也就是说, 要将一个 Byte 类型值赋给一个 Int 类型的变量需要进行显式类型转换:

val b: Byte = 1 // 这是 OK 的, 因为编译器会对字面值进行静态检查
// val i: Int = b // 编译错误
val i1: Int = b.toInt()

所有的数值类型都可以转换为其他类型:

  • toByte(): Byte
  • toShort(): Short
  • toInt(): Int
  • toLong(): Long
  • toFloat(): Float
  • toDouble(): Double

大多数情况下并不需要明确的的类型转换, 因为类型可以通过代码上下文自动推断得到, 而且数学运算符都进行了重载(overload), 可以适应各种数值类型的参数, 比如:

val l = 1L + 3 // Long 类型 + Int 类型, 结果为 Long 类型

数值类型的运算符(Operation)

Kotlin 对数值类型支持标准的数学运算符(operation): +, -, *, /, %. 这些运算符定义为相应的数值类上的成员函数:

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

你也可以对自己的类覆盖这些运算符. 详情请参见 操作符重载(Operator overloading).

整数除法

整数值之间的除法返回的永远是整数值. 所有的小数部分都会被抛弃.

fun main() {
//sampleStart
    val x = 5 / 2
    //println(x == 2.5) // 错误: 不能在 'Int' 和 'Double' 类型值之间使用 '==' 操作符
    println(x == 2)
//sampleEnd
}

对任何两种整数类型之间的除法都是如此:

fun main() {
//sampleStart
    val x = 5L / 2
    println(x == 2L)
//sampleEnd
}

如果要返回浮点类型的结果, 需要将其中一个操作数显式转换为浮点类型:

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

位运算符

Kotlin 对整数值提供了一组 位运算符. 这些运算符直接对数值的二进制表达的位(bit)进行操作. 位运算符表达为函数, 可以通过中缀表示法调用. 只能用于 IntLong:

val x = (1 shl 2) and 0x000FF000

以下是位运算符的完整列表:

  • shl(bits) – 带符号左移
  • shr(bits) – 带符号右移
  • ushr(bits) – 无符号右移
  • and(bits) – 按位与(AND)
  • or(bits) – 按位或(OR)
  • xor(bits) – 按位异或(XOR)
  • inv() – 按位取反

浮点值的比较

本节我们讨论的浮点值操作包括:

  • 相等判断: a == b 以及 a != b
  • 比较操作符: a < b, a > b, a <= b, a >= b
  • 浮点值范围(Range) 的创建, 以及范围检查: a..b, x in a..b, x !in a..b

如果操作数 ab 的类型能够静态地判定为 FloatDouble(或者可为 null 值的 Float?Double?), (比如, 类型明确声明为浮点值, 或者由编译器推断为浮点值, 或者通过智能类型转换变为浮点值), 那么此时对这些数值, 或由这些数值构成的范围的操作, 将遵循 IEEE 754 浮点数值运算标准.

但是, 为了支持使用泛型的情况, 并且支持完整的排序功能, 如果操作数 不能 静态地判定为浮点值类型, 那么判定结果会不同. 例如, Any, Comparable<...>, 或 Collection<T> 类型. 对于这样的情况, 对这些浮点值的操作将使用 FloatDouble 类中实现的 equalscompareTo 方法. 因此判定结果是:

  • NaN 会被判定为等于它自己
  • NaN 会被判定为大于任何其他数值, 包括正无穷大(POSITIVE_INFINITY)
  • -0.0 会被判定为小于 0.0

下面是一段的示例程序, 演示静态地判定为浮点值类型的操作数(Double.NaN) 与 不能 静态地判定为浮点值类型的操作数 (listOf(T)) 之间的动作差别.

fun main() {
    //sampleStart
    // 操作数静态地判定为浮点值类型
    println(Double.NaN == Double.NaN)                 // 输出结果为 false
    // 操作数 不能 静态地判定为浮点值类型
    // 因此 NaN 等于它自己
    println(listOf(Double.NaN) == listOf(Double.NaN)) // 输出结果为 true

    // 操作数静态地判定为浮点值类型
    println(0.0 == -0.0)                              // 输出结果为 true
    // 操作数 不能 静态地判定为浮点值类型
    // 因此 -0.0 小于 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]
    //sampleEnd
}