数字
Kotlin 的数字类型表示:
使用数字类型来存储和处理数值数据,例如在算术、计数器、测量和其他计算中。
选择数字类型
在大多数情况下,你可以参考以下规则来为你的任务确定正确的数字类型:
- 使用
Int表示整数。 - 使用
Long表示超出Int范围的整数。 - 使用
Double表示十进制数字。 - 当可以接受或需要较低精度时,使用
Float。 - 当 API 或数据格式有要求时,使用
Byte和Short。
整数类型
Kotlin 提供了四种具有不同大小和值范围的整数类型:
| 类型 | 大小(位) | 最小值 | 最大值 |
|---|---|---|---|
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) |
声明整数值
Kotlin 支持以下整数值的字面量形式:
- 十进制:
123 - 十六进制:
0x0F - 二进制:
0b00001011
Kotlin 不支持八进制字面量。
要声明数值,请显式指定类型:
val one: Int = 1
// 使用下划线提高可读性
val oneBillion: Long = 1_000_000_000
val hexBytes: Int = 0xFF_EC_DE_5E
val bytes: Int = 0b11010010_01101001_10010100_10010010
val oneByte: Byte = 1
val oneShort: Short = 1你也可以添加 L 后缀来声明 Long 值:
val oneLong = 1L当你显式声明数值类型时,编译器会检查该值是否符合该类型的范围:
// 值符合 Byte 范围
val oneByte: Byte = 1
// 错误:该值不符合 Byte 范围
val tooBig: Byte = 128当你未指定数值类型时,如果该值符合 Int 范围,Kotlin 会推断为 Int。否则,Kotlin 会推断为 Long:
val million = 1_000_000 // Int
val threeBillion = 3_000_000_000 // Long如果某个值可能缺失,请使用可空类型:
val maybeAbsent: Int? = null浮点类型
对于带有小数部分的数字,Kotlin 提供了 Float 和 Double。
浮点类型遵循 IEEE 754 标准。 Float 反映了单精度。Double 反映了双精度。
这些浮点类型的大小和精度不同:
| 类型 | 大小(位) | 有效位数 | 指数位数 | 十进制位数 |
|---|---|---|---|---|
Float | 32 | 24 | 8 | 6-7 |
Double | 64 | 53 | 11 | 15-16 |
声明浮点值
要声明浮点字面量,请包含小数点 (.) 或使用指数表示法:
val pi = 3.14
val avogadro = 6.02214076e23默认情况下,Kotlin 将浮点字面量推断为 Double。 要声明 Float,请添加 f 或 F 后缀:
val pi = 3.14 // Double
val eFloat = 2.7182818284f // FloatKotlin 会对包含超过
Float所能存储精度的Float字面量进行舍入。
如果某个值可能缺失,请使用可空类型:
val maybeAbsent: Double? = null算术操作
Kotlin 支持对数字的标准算术操作:+、-、*、/ 和 %。
使用这些运算符执行常用计算:
fun main() {
println(1 + 2) // 3
println(2_500_000_000L - 1L) // 2499999999
println(3.14 * 2.71) // 8.5094
println(10.0 / 3) // 3.3333333333333335
}你可以在自定义数字类中重写这些运算符。 有关更多信息,请参阅运算符重载。
整数除法
整数值之间的除法始终返回整数结果。编译器会丢弃小数部分:
fun main() {
val intValue = 5 / 2
println(intValue) // 2
val longValue = 5L / 2
println(longValue) // 2
}要返回浮点结果,请使至少一个操作数为 Float 或 Double:
fun main() {
val a = 5 / 2.0
println(a) // 2.5
val b = 5 / 2.toDouble()
println(b) // 2.5
}类型转换
数值类型不是彼此的子类型。Kotlin 要求进行显式转换,以避免静默数据丢失和意外行为。
例如,一个期望 Double 的函数不能在不转换的情况下接受 Int 或 Float 值:
fun main() {
fun printDouble(x: Double) {
print(x)
}
val x = 1.0
val xInt = 1
val xFloat = 1.0f
val one: Double = 1 // 错误:初始值设定项类型不匹配
printDouble(x) // 成功
printDouble(xInt) // 错误:实参类型不匹配
printDouble(xFloat) // 错误:实参类型不匹配
}所有数字类型都支持转换为其他数字类型。 要将数字转换为另一种类型,请使用显式转换函数:
toByte()toShort()toInt()toLong()toFloat()toDouble()
例如,以下代码将 Int 值转换为 Double:
fun main() {
val intValue: Int = 1
val doubleValue = intValue.toDouble()
println(doubleValue) // 1.0
}当你将浮点值转换为整数类型时,编译器会丢弃小数部分:
fun main() {
val d: Double = 1.5
val l: Long = d.toLong()
println(l) // 1
}混合数值表达式
Kotlin 不支持赋值或函数实参的隐式转换。 但是,你可以在算术表达式中组合不同的数字类型。在这种情况下, Kotlin 会根据操作数类型确定结果类型, 且算术运算符会自动处理转换:
val intNumber: Int = 1
val longNumber: Long = 1000
val result = intNumber + longNumber // 1001, Long如果你尝试将结果赋值给较小的类型,编译器会报错:
val intNumber: Int = 1
val longNumber: Long = 1000
val result: Int = intNumber + longNumber
// 错误:初始值设定项类型不匹配数据溢出
数字类型只能表示其定义范围内的值。
如果操作结果超出该范围,则会发生溢出。 如果你将一个值转换为较小的数字类型,转换后的值可能无法保留 原始数值。
即使编译器接受了这种行为,它也可能影响你的代码结果。
操作中的溢出
每种整数类型只能存储其定义范围内的值。当算术操作的结果超过该范围时, 会发生数据溢出:
fun main(){
val intNumber: Int = 2147483647
// Int 的最大值是 2147483647
println(intNumber + 1) // -2147483648
}在这里,结果发生了回绕,因为该值不再能容纳在 Int 中。
发生整数溢出时,编译器不会自动报错。
取负中的溢出
在取负期间也可能发生溢出。 例如,你无法将 Int.MIN_VALUE 的正数对应值表示为 Int。
fun main(){
val min = Int.MIN_VALUE
println(-min) // -2147483648
}缩窄转换
当你将一个值转换为较小的整数类型时, 结果可能无法保留原始数值:
fun main() {
val large: Int = 130
val narrowed: Byte = large.toByte()
println(narrowed) // -126
}然而,由于浮点类型遵循 IEEE 754 标准, 非常大的结果可能会变成 Infinity(无穷大):
fun main() {
println(Double.MAX_VALUE * 2) // Infinity
}按位操作
Kotlin 为 Int 和 Long 提供了按位操作。这些操作由一组中缀函数和 inv() 表示。
fun main() {
val x = 1
println(x shl 2) // 4
println(x and 0x000FF000) // 0
}按位操作包括:
shl()– 有符号左移shr()– 有符号右移ushr()– 无符号右移and()– 按位与or()– 按位或xor()– 按位异或inv()– 按位取反
浮点数比较
在 Kotlin 中,浮点数比较取决于操作数的静态类型。
当操作数被静态已知为 Float 或 Double 时, 对数字及其形成的区间的操作 遵循 IEEE 754 浮点算术标准。
然而,在泛型用例中(例如 Any、Comparable<...> 或 Collection<T>), 对于非静态类型为浮点数的操作数,其行为会有所不同。在这些情况下,Kotlin 使用 Float 和 Double 的 equals() 和 compareTo() 实现。
因此:
NaN被认为等于其自身NaN被认为大于包括POSITIVE_INFINITY在内的任何其他元素-0.0被认为小于0.0
以下示例显示了静态类型为浮点数的操作数 与通过泛型类型使用的操作数之间的区别:
fun generalizedEquals(a: Any, b: Any): Boolean {
return a == b
}
fun main() {
// 静态类型为浮点数的操作数
println(Double.NaN == Double.NaN) // false
println(0.0 == -0.0) // true
// 通过非浮点静态类型使用的操作数
println(generalizedEquals(Double.NaN, Double.NaN)) // true
println(generalizedEquals(0.0, -0.0)) // false
}JVM 上的数字装箱与缓存
在 JVM 上,不可空数值通常使用基本类型存储,例如 int、long 或 double。 然而,当你使用泛型或像 Int? 这样的可空数值类型时,该值会被装箱并 表示为一个对象。
JVM 通过缓存小数字的装箱表示形式来应用内存优化技术。因此, 具有相同值的装箱数字可以是引用相等的。
例如,JVM 缓存了 -128 到 127 范围内的装箱 Integer 值。因此,以下 代码返回 true:
fun main() {
val score: Int = 100
val savedScore: Int? = score
val displayedScore: Int? = score
println(savedScore === displayedScore) // true
}对于缓存范围之外的值,装箱值是独立的对象。在这种情况下, 即使它们的值是结构相等的,它们也不是引用相等的。 出于这个原因,请使用 == 来比较数值:
fun main() {
val score: Int = 10000
val savedScore: Int? = score
val displayedScore: Int? = score
println(savedScore === displayedScore) // false
println(savedScore == displayedScore) // true
}