5.4.2 数值
原文链接: https://kotlinlang.org/docs/numbers.html
5.4.2 数值
Kotlin 的数值类型表示:
使用数值类型来存储和处理数值数据,例如用于算术运算、计数器、测量以及其他计算。
选择数值类型
在大多数情况下,你可以参考以下规则来确定适合你的任务的数值类型:
- 整数使用
Int。 - 超出
Int 范围的整数使用 Long。 - 小数使用
Double。 - 当较低精度可以接受或必需时使用
Float。 - 当 API 或数据格式要求时使用
Byte 和 Short。
提示: Kotlin 还以 Beta 特性形式提供了无符号整数类型。
整数类型
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 不支持八进制字面量。
要声明一个数值,请显式指定类型:
1
2
3
4
5
6
7
8
9
| val one: Int = 1
// Use underscores to improve readability
val oneBillion: Long = 1_000_000_000
val hexBytes: Int = 0x7F_EC_DE_5E
val bytes: Int = 0b01010010_01101001_10010100_10010010
val oneByte: Byte = 1
val oneShort: Short = 1
|
你也可以加上 L 后缀来声明 Long 值:
当你显式声明数值类型时,编译器会检查该值是否落在该类型的取值范围内:
1
2
3
4
5
| // Value fits in Byte
val oneByte: Byte = 1
// Error: the value does not fit in Byte
val tooBig: Byte = 128
|
当你不指定数值类型时,如果该值落在 Int 范围内,Kotlin 会推断为 Int;否则推断为 Long:
1
2
| val million = 1_000_000 // Int
val threeBillion = 3_000_000_000 // Long
|
如果值可能缺失,请使用可空类型:
1
| val maybeAbsent: Int? = null
|
浮点类型
对于带小数部分的数值,Kotlin 提供了 Float 和 Double。
浮点类型遵循 IEEE 754 标准。Float 对应单精度,Double 对应双精度。
浮点类型在大小和精度上有所不同:
| 类型 | 大小(位) | 有效位 | 指数位 | 十进制位数 |
| Float | 32 | 24 | 8 | 6-7 |
| Double | 64 | 53 | 11 | 15-16 |
声明浮点值
要声明浮点字面量,请包含小数点(.)或使用指数表示法:
1
2
| val pi = 3.14
val avogadro = 6.02214076e23
|
默认情况下,Kotlin 把浮点字面量推断为 Double。要声明 Float,请加上 f 或 F 后缀:
1
2
| val pi = 3.14 // Double
val eFloat = 2.7182817f // Float
|
注意: 对于精度超出 Float 所能存储范围的 Float 字面量,Kotlin 会进行舍入。
如果值可能缺失,请使用可空类型:
1
| val maybeAbsent: Double? = null
|
算术运算
Kotlin 支持对数值进行标准算术运算:+、-、*、/ 和 %。
使用这些运算符执行常见计算:
1
2
3
4
5
6
| 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
}
|
结果类型取决于操作数的类型。更多内容请参阅混合数值表达式。
提示: 你可以在自定义的数值类中重写这些运算符。更多信息请参阅运算符重载。
整数除法
整数值之间的除法总是返回整数结果。编译器会丢弃小数部分:
1
2
3
4
5
6
7
| fun main() {
val intValue = 5 / 2
println(intValue) // 2
val longValue = 5L / 2
println(longValue) // 2
}
|
要返回浮点结果,请让至少一个操作数是 Float 或 Double:
1
2
3
4
5
6
7
| fun main() {
val a = 5 / 2.0
println(a) // 2.5
val b = 5 / 2.toDouble()
println(b) // 2.5
}
|
类型转换
数值类型之间并不是子类型关系。Kotlin 要求显式转换,以避免静默的数据丢失和意外行为。
例如,期望 Double 的函数无法在不转换的情况下接受 Int 或 Float 值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| fun main() {
fun printDouble(x: Double) {
print(x)
}
val x = 1.0
val xInt = 1
val xFloat = 1.0f
val one: Double = 1 // Error: initializer type mismatch
printDouble(x) // OK
printDouble(xInt) // Error: argument type mismatch
printDouble(xFloat) // Error: argument type mismatch
}
|
所有数值类型都支持转换为其他数值类型。要把一个数值转换为另一种类型,请使用显式转换函数:
toByte()toShort()toInt()toLong()toFloat()toDouble()
例如,下面的代码把 Int 值转换为 Double:
1
2
3
4
5
6
| fun main() {
val intValue: Int = 1
val doubleValue = intValue.toDouble()
println(doubleValue) // 1.0
}
|
当你把浮点值转换为整数类型时,编译器会丢弃小数部分:
1
2
3
4
5
6
| fun main() {
val d: Double = 1.5
val l: Long = d.toLong()
println(l) // 1
}
|
混合数值表达式
Kotlin 不支持在赋值或函数实参中进行隐式转换。不过,你可以在算术表达式中组合不同的数值类型。在这种情况下,Kotlin 会根据操作数的类型确定结果类型,算术运算符会自动处理转换:
1
2
3
| val intNumber: Int = 1
val longNumber: Long = 1000
val result = intNumber + longNumber // 1001, Long
|
如果你试图把结果赋给更小的类型,编译器会报错:
1
2
3
4
| val intNumber: Int = 1
val longNumber: Long = 1000
val result: Int = intNumber + longNumber
// Error: Initializer type mismatch
|
整数字面量类型
在类型推断过程中,直到上下文确定具体类型之前,Kotlin 会把不带后缀的整数字面量当作一种特殊的整数字面量类型(ILT):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| fun List<Any>.log() {
println(joinToString(" | ") { it::class.simpleName ?: "Unknown" })
}
fun main() {
listOf(1, 2).log()
// Int | Int
listOf(1L, 2L).log()
// Long | Long
// Compiler interprets 1 as an ILT and resolves it to Long
listOf(1, 2L).log()
// Long | Long
// .toInt() converts the literal to Int
listOf(1.toInt(), 2L).log()
// Int | Long
}
|
对于 Int 和 Long 值尤其容易忽略这一点,因为它们在运行期的字符串表示相同。要避免这个问题,请指定期望的类型或显式转换值:
1
2
3
4
5
6
7
8
9
10
11
12
13
| fun List<Any>.log() {
println(joinToString(" | ") { it::class.simpleName ?: "Unknown" })
}
fun main() {
val longValues: List<Long> = listOf(1, 2L)
longValues.log()
// Long | Long
val numberValues: List<Number> = listOf(1.toInt(), 2L)
numberValues.log()
// Int | Long
}
|
你也可以使用显式类型来捕获意外的类型推断:
1
2
3
4
| fun main() {
val intValues: List<Int> = listOf(1, 2L)
// Error: initializer type mismatch
}
|
提示: 了解更多关于整数字面量类型的内容。
数据溢出
数值类型只能表示其既定范围内的值。
如果运算结果落在该范围之外,就会发生溢出。如果你把值转换为更小的数值类型,转换后的值可能不再保留原来的数值。
即使编译器接受这样的代码,这种行为也可能影响代码的结果。
运算中的溢出
每种整数类型都只能存储其既定范围内的值。当算术运算的结果超出该范围时,就会发生数据溢出:
1
2
3
4
5
| fun main(){
val intNumber: Int = 2147483647
// Max Int value is 2147483647
println(intNumber + 1) // -2147483648
}
|
这里的值发生了回绕,因为它不再能放入 Int。
注意: 当发生整数溢出时,编译器不会自动报错。
取负中的溢出
取负时也可能发生溢出。例如,你无法把 Int.MIN_VALUE 的正数对应值表示为 Int。
1
2
3
4
| fun main(){
val min = Int.MIN_VALUE
println(-min) // -2147483648
}
|
收窄转换
当你把值转换为更小的整数类型时,结果可能不再保留原来的数值:
1
2
3
4
5
6
| fun main() {
val large: Int = 130
val narrowed: Byte = large.toByte()
println(narrowed) // -126
}
|
不过,由于浮点类型遵循 IEEE 754 标准,非常大的结果可能变成 Infinity:
1
2
3
| fun main() {
println(Double.MAX_VALUE * 2) // Infinity
}
|
位运算
Kotlin 为 Int 和 Long 提供了位运算。这些运算由一组中缀函数和 inv() 表示。
1
2
3
4
5
6
| 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
下面的示例展示了静态类型为浮点数的操作数,与通过泛型类型使用的操作数之间的差异:
1
2
3
4
5
6
7
8
9
10
11
12
13
| fun generalizedEquals(a: Any, b: Any): Boolean {
return a == b
}
fun main() {
// Operands statically typed as floating-point numbers
println(Double.NaN == Double.NaN) // false
println(0.0 == -0.0) // true
// Operands used through a non-floating-point static type
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:
1
2
3
4
5
6
7
| fun main() {
val score: Int = 100
val savedScore: Int? = score
val displayedScore: Int? = score
println(savedScore === displayedScore) // true
}
|
对于缓存范围之外的值,装箱后的值是彼此独立的对象。在这种情况下,即使它们的值结构相等,它们也不是引用相等的。因此,比较数值时请使用 ==:
1
2
3
4
5
6
7
8
| fun main() {
val score: Int = 10000
val savedScore: Int? = score
val displayedScore: Int? = score
println(savedScore === displayedScore) // false
println(savedScore == displayedScore) // true
}
|