数字

整数类型

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)

当初始化一个没有显式指定类型的变量时,编译器会自动推断为自 Int 起足以表示该值的最小类型。 如果不超过 Int 的表示范围,那么类型是 Int。 如果超过了,那么类型是 Long。 如需显式指定 Long 值,请给该值追加后缀 L。 显式指定类型会触发编译器检测该值是否超出指定类型的表示范围。

  1. val one = 1 // Int
  2. val threeBillion = 3000000000 // Long
  3. val oneLong = 1L // Long
  4. val oneByte: Byte = 1

除了整数类型之外,Kotlin 还提供无符号整数类型。 更多信息请参见无符号整数类型

数字 - 图1

浮点类型

对于实数,Kotlin 提供了浮点类型 FloatDouble 类型,遵循 IEEE 754 标准Float 表达 IEEE 754 单精度,而 Double 表达双精度

这两个类型的大小不同,并为两种不同精度的浮点数提供存储:

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

可以使用带小数部分的数字初始化 DoubleFloat 变量。 小数部分与整数部分之间用句点(.)分隔 对于以小数初始化的变量,编译器会推断为 Double 类型:

  1. val pi = 3.14 // Double
  2. // val one: Double = 1 // 错误:类型不匹配
  3. val oneDouble = 1.0 // Double

如需将一个值显式指定为 Float 类型,请添加 fF 后缀。 如果这样的值包含多于 6~7 位十进制数,那么会将其舍入:

  1. val e = 2.7182818284 // Double
  2. val eFloat = 2.7182818284f // Float,实际值为 2.7182817

与一些其他语言不同,Kotlin 中的数字没有隐式拓宽转换。 例如,具有 Double 参数的函数只能对 Double 值调用,而不能对 FloatInt 或者其他数字值调用:

  1. fun main() {
  2. fun printDouble(d: Double) { print(d) }
  3. val i = 1
  4. val d = 1.0
  5. val f = 1.0f
  6. printDouble(d)
  7. // printDouble(i) // 错误:类型不匹配
  8. // printDouble(f) // 错误:类型不匹配
  9. }

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

数字字面常量

数值常量字面值有以下几种:

  • 十进制: 123
    • Long 类型用大写 L 标记: 123L
  • 十六进制: 0x0F
  • 二进制: 0b00001011

Kotlin 不支持八进制。

数字 - 图2

Kotlin 同样支持浮点数的常规表示方法:

  • 默认 double:123.5123.5e10
  • Float 用 f 或者 F 标记: 123.5f

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

  1. val oneMillion = 1_000_000
  2. val creditCardNumber = 1234_5678_9012_3456L
  3. val socialSecurityNumber = 999_99_9999L
  4. val hexBytes = 0xFF_EC_DE_5E
  5. val bytes = 0b11010010_01101001_10010100_10010010

无符号整数字面值也有特殊标记。
更多内容请参阅无符号整型字面值

数字 - 图3

JVM 平台的数字表示

在 JVM 平台数字存储为原生类型 intdouble 等。 例外情况是当创建可空数字引用如 Int? 或者使用泛型时。 在这些场景中,数字会装箱为 Java 类 IntegerDouble 等。

对相同数字的可为空引用可能会引用不同的对象:

  1. fun main() {
  2. //sampleStart
  3. val a: Int = 100
  4. val boxedA: Int? = a
  5. val anotherBoxedA: Int? = a
  6. val b: Int = 10000
  7. val boxedB: Int? = b
  8. val anotherBoxedB: Int? = b
  9. println(boxedA === anotherBoxedA) // true
  10. println(boxedB === anotherBoxedB) // false
  11. //sampleEnd
  12. }

由于 JVM 对 -128127 的整数(Integer)应用了内存优化,因此,a 的所有可空引用实际上都是同一对象。但是没有对 b 应用内存优化,所以它们是不同对象。

另一方面,它们仍然相等:

  1. fun main() {
  2. //sampleStart
  3. val b: Int = 10000
  4. println(b == b) // 输出“true”
  5. val boxedB: Int? = b
  6. val anotherBoxedB: Int? = b
  7. println(boxedB == anotherBoxedB) // 输出“true”
  8. //sampleEnd
  9. }

显式数字转换

由于不同的表示方式,较小类型并不是较大类型的子类型。 如果它们是的话,就会出现下述问题:

  1. // 假想的代码,实际上并不能编译:
  2. val a: Int? = 1 // 一个装箱的 Int (java.lang.Integer)
  3. val b: Long? = a // 隐式转换产生一个装箱的 Long (java.lang.Long)
  4. print(b == a) // 惊!这将输出“false”鉴于 Long 的 equals() 会检测另一个是否也为 Long

所以会悄无声息地失去相等性,更别说同一性了。

因此较小的类型不能 隐式转换为较大的类型。 这意味着把 Byte 型值赋给一个 Int 变量必须显式转换:

  1. fun main() {
  2. //sampleStart
  3. val b: Byte = 1 // OK, 字面值会静态检测
  4. // val i: Int = b // 错误
  5. val i1: Int = b.toInt()
  6. //sampleEnd
  7. }

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

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

很多情况都不需要显式类型转换,因为类型会从上下文推断出来, 而算术运算会有重载做适当转换,例如:

  1. val l = 1L + 3 // Long + Int => Long

数字运算

Kotlin支持数字运算的标准集:+-*/%。它们已定义为相应的类成员:

  1. fun main() {
  2. //sampleStart
  3. println(1 + 2)
  4. println(2_500_000_000L - 1L)
  5. println(3.14 * 2.71)
  6. println(10.0 / 3)
  7. //sampleEnd
  8. }

还可以为自定义类覆盖这些操作符。详情请参见操作符重载

整数除法

整数间的除法总是返回整数。会丢弃任何小数部分。

  1. fun main() {
  2. //sampleStart
  3. val x = 5 / 2
  4. //println(x == 2.5) // ERROR: Operator '==' cannot be applied to 'Int' and 'Double'
  5. println(x == 2)
  6. //sampleEnd
  7. }

对于任何两个整数类型之间的除法来说都是如此:

  1. fun main() {
  2. //sampleStart
  3. val x = 5L / 2
  4. println(x == 2L)
  5. //sampleEnd
  6. }

如需返回浮点类型,请将其中的一个参数显式转换为浮点类型:

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

位运算

Kotlin 对整数提供了一组位运算。它们直接使用数字的比特表示在二进制级别进行操作。 位运算有可以通过中缀形式调用的函数表示。只能应用于 IntLong

  1. val x = (1 shl 2) and 0x000FF000

这是完整的位运算列表:

  • 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> 类型。 这种情况下,这些操作使用为 FloatDouble 实现的 equalscompareTo。 因此:

  • 认为 NaN 与其自身相等
  • 认为 NaN 比包括正无穷大(POSITIVE_INFINITY)在内的任何其他元素都大
  • 认为 -0.0 小于 0.0

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

  1. fun main() {
  2. //sampleStart
  3. // 静态类型作为浮点数的操作数
  4. println(Double.NaN == Double.NaN) // false
  5. // 静态类型并非作为浮点数的操作数
  6. // 所以 NaN 等于它本身
  7. println(listOf(Double.NaN) == listOf(Double.NaN)) // true
  8. // 静态类型作为浮点数的操作数
  9. println(0.0 == -0.0) // true
  10. // 静态类型并非作为浮点数的操作数
  11. // 所以 -0.0 小于 0.0
  12. println(listOf(0.0) == listOf(-0.0)) // false
  13. println(listOf(Double.NaN, Double.POSITIVE_INFINITY, 0.0, -0.0).sorted())
  14. // [-0.0, 0.0, Infinity, NaN]
  15. //sampleEnd
  16. }