仓颉编程快速上手
上QQ阅读APP看书,第一时间看更新

3.1.5 避免算术运算中的类型错误

在对仓颉数值类型的数据进行算术运算时,关于数据类型,有以下5点需要注意。

1.算术操作符对操作数类型的要求

对于一元算术操作符“-(负号)”,仓颉对其操作数的类型没有要求,只要其操作数为数值类型即可。

对于以下算术表达式:

操作数A 二元算术操作符 操作数B

如果式中的二元算术操作符不是“**”,那么仓颉要求操作数A和操作数B必须是相同的数值类型,否则会引发编译错误。

注意

“%”要求操作数A和操作数B必须是相同的整数类型。

举例如下:

main() {
let intA: Int8 = 8
let intB: Int16 = 10
println(intA * 4i8)  // Int8类型和Int8类型的运算
println(intB + 4i8)  // 编译错误:无法对Int16和Int8类型使用二元操作符“+”
}

如果式中的二元算术操作符是“**”,那么操作数A和操作数B的类型必须符合表3-5中所示的3种情况中的一种,否则会引发编译错误。

表3-5 乘方运算的操作数类型

图片表格

举例如下:

main() {
let intC: Int64 = 3
println(intC ** 2u64)  // 操作数A为Int64类型,操作数B为UInt64类型
println(intC ** 2i64)  // 编译错误:无法对Int64和Int64类型使用二元操作符“**”
}

提示

以上算术操作符对操作数类型的要求,均是在没有操作符重载的前提下。关于操作符重载的相关知识参见第10章。

2.算术运算结果的数据类型

对于以下算术表达式:

-操作数A

以及以下算术表达式:

操作数A 二元算术操作符 操作数B

运算结果的数据类型均与操作数A保持一致。假设有两个Int64类型的变量x和y,以下表达式的运算结果也是Int64类型:

-x
x + y
x * y
x ** 2u64

在进行算术运算时,必须要注意各种数值类型的表示范围,避免溢出错误。举例如下:

3i8 + 125i8  // 溢出

在以上算术表达式中,Int8类型的3加上Int8类型的125,其结果应该为128,但是由于Int8类型的表示范围为-128~127,因此结果溢出了。

3.数值类型字面量和变量的类型推断

前面介绍过,在没有类型上下文的情况下,整数类型字面量会被推断为Int64类型,浮点类型字面量会被推断为Float64类型。举例如下:

println(3)  // 字面量3被推断为Int64类型
println(9.5)  // 字面量9.5被推断为Float64类型

如果有类型上下文,那么编译器会自动根据类型上下文来推断数值类型字面量的类型。举例如下:

main() {
let x: Float32 = 9.98
let y = x + 1.5
}

在这段代码中,x是Float32类型,因此字面量9.98被推断为Float32类型。在表达式x + 1.5中,由于加法运算要求两个操作数的类型是一致的,而x为Float32类型,因此字面量1.5也被推断为Float32类型。

对于缺省了数据类型的变量声明,编译器也会自动推断变量的数据类型。例如,在上面的代码中,变量y在声明时缺省了数据类型,而其初始值x + 1.5的类型为Float32,因此y将被推断为Float32类型。再举一个例子:

let z = 0.99

在以上代码中,由于缺少类型上下文,字面量0.99将被推断为Float64类型,因此根据初始值的类型,在声明时缺省了数据类型的变量z将会被推断为Float64类型。

4.数值类型的类型转换

仓颉对算术运算的操作数类型的要求十分严格,除“**”运算外,不同类型的操作数之间不能进行算术运算。

如果在某些情况下确实需要对不同类型的操作数进行运算,可以考虑对操作数的类型进行转换。举例如下:

main() {
    var mile: Int64 = 3  // 表示英里
    var kilometer: Float64  // 表示公里
kilometer = 1.609344 * mile  // 编译错误
}

以上示例程序的作用是将英里换算成公里(1英里 = 1.609344公里)。由于mile和kilometer的类型不一致,因此不能直接进行乘法运算。此时,可以将表示英里的变量值转换为Float64类型,再进行乘法运算。

仓颉不支持不同类型之间的隐式转换,类型转换必须显式地进行。仓颉支持使用以下方式得到一个值为e的T类型的实例

T(e)

其中,T可以是各种数值类型,如Int8、Int16、Float32等,e可以是一个数值类型的表达式。

提示

当我们说e是T类型的实例时,表示e是一个T类型的值。

举例如下:

Int64(9i8)  // 结果为Int64类型的9
Float32(200u16)  // 结果为Float32类型的200.0

回到换算英里和公里的例子,我们可以使用如下代码计算出英里对应的公里数:

kilometer = 1.609344 * Float64(mile)

在各种数值类型之间进行类型转换时,需要遵循一定的转换规则。

首先,在整数类型之间进行转换时,要确保待转换的整数必须要在目标整数类型的表示范围之内。举例如下:

Int64(200u8)  // 将UInt8类型的200转换为Int64类型,结果为Int64类型的200
Int8(200)  // 将Int64类型的200转换为Int8类型,溢出错误

然后,在浮点类型之间转换时也需要注意不同浮点类型的表示范围,如果是精度高的浮点类型向精度低的浮点类型转换,将会出现精度损失。举例如下:

main() {
let x: Float64 = 987.654321
let y = Float16(x)
    println(x)  // 输出:987.654321
    println(y)  // 输出:987.500000,损失了精度
}

最后,在整数类型和浮点类型相互转换时,要确保待转换的类型必须要在目标类型的表示范围之内。另外,如果是浮点类型转换为整数类型,那么浮点数的小数部分会被直接截断,只保留整数部分。举例如下:

Float64(2023)  // 整数类型向浮点类型转换,结果为2023.0
Int16(7.9)  // 浮点类型向整数类型转换,结果为7

5.建议使用的数值类型

对整数类型来说,Int64类型的表示范围最大,不容易引发溢出错误。另外,在没有类型上下文的情况下,Int64类型是仓颉默认的整数类型,而使用默认的类型可以避免在运算中进行不必要的类型转换。因此,在Int64类型适合的情况下,建议首选Int64类型。

对浮点类型来说,除了与整数类型相同的原因,由于浮点类型在存储和运算时都会产生一定的误差,因此精度高的浮点类型优于精度低的浮点类型。在多种浮点类型都适合的情况下,建议首选Float64类型。