一、Lua数值类型发展
- 在Lua 5.2及之前的版本中,所有的数值都以双精度浮点格式表示
- 从Lua 5.3版本开始,Lua语言为数值格式提供了两种选择:
- 整型值:称为interger的64位整型
- 浮点型值:称为float的双精度浮点类型
二、数值常量表示
- 下面是一些合理的数值常量表示:
4
0.4
- 其中还可以使用科学记数法:
- 格式为一个可选的十进制部分外加一个可选的十进制指数部分)
- 科学记数法最终是一个浮点数
4.57e-3
0.3e12
5E+20
三、类型判断
type()函数
- 使用type()函数可以获取整型值和浮点型值的类型,返回的都是number(表示数值类型)
type(3) type(3.5) type(3.14e3)
math.type()函数
- 如果想要区分整型值和浮点型值,可以使用这个函数
math.type(3) math.type(3.5) math.type(3.14e3)
四、十六进制表示
- Lua也支持以0x开头的十六进制常量
- Lua不仅支持十六进制的整型、还支持十六进制的浮点数
- 例如:
0xff
0x1A3
0x0.2
- 十六进制浮点数还可以由小数部分和以p或P开头的指数部分组成(Lua 5.2引入的)。例如:
0x1p-1
0xa.bp2
- 通过string.format()的%a参数可以对上面的这种格式进行格式化的输出:
string.format("%a", 419)
string.format("%a", 0.1)
- 虽然这种格式很难阅读,但是这种格式可以保留所有浮点数的精度,并且比十进制的转换速度更快
五、算术运算
- Lua支持的算术运算有:
- 加(+)、减(-)、乘(*)、除(/)
- 取负数(-)
- 取整除法/floor除法(//)
- 取模(%)
- 指数运算
- 幂运算(^)
- Lua 5.3引入整型的主要建议是:开发人员要么选择忽略整型和浮点型二者之间的不同,要么就完整地控制每一个数值的表示。因此,所有的算术操作符不论操作整型值还是浮点型值,结果都是一样的
整型值和浮点型值之间的算术运算
- 如果两个操作数都是整型值,则结果也是整型值;否则就是浮点型值
- 当两个操作数的类型不同时,运算之前会先将整型值转换为浮点型值
- 例如:
13 + 15 13.0 + 15 13 + 15.0 13.0 + 15.0 1 * 2 1 * 2.0
除法的注意事项
- 由于两个整数相处并不一定是整数,因此当两个数进行相除时,interger都会转换为浮点数(即使两个操作数都是整数液转换)
- 并且除法运算的结果也是浮点数
- 例如:
3.0 / 2.0 3 / 2
floor除法
- floor除法会对得到的商向负无穷取整,从而保证结果是一个整数
- 这样,floor除法就可以与其他算术运算一样遵循同样的规则:如果操作数都是整型值,那么结果就是整型值,否则就是浮点数类型
- 例如:
3 // 2 3.0 // 2 6 // 2 6.0 // 2.0 -9 // 2 1.5 // 0.5
取负数运算
- 返回一个值的负数
- 使用的时候注意,对要操作的表达式的结果要加上括号,否则有点像是定义一个负数的感觉
- 例如:
-1.0 -1 -(-1) -- 这个是注释, 而不是两次取负 --1 - (3 * 6.0)
取模运算
- 取模运算的结果类型与上面介绍的一样,如果两个操作数都是整型值则返回整型;否则返回浮点数
- 例如:
a = 10 b = 2 a % b == a - ((a // b) * b)
- 对于实数类型(浮点数)而言,取模运算有一些不同,例如x-x%0.01恰好是x保留2位小数的结果,x-x%0.001恰好是x保留3位小数的结果
x = math.pi x - x%0.01 x - x%0.001
- 演示案例:我们可以使用取模运算检查某辆车在拐过指定的角度后是否能够原路返回。假设使用度作为角度的单位,那么我们可以使用下面的函数
local tolerance = 10 function isturnback(angle) angle = angle % 360 return (math.abs(angle - 180) < tolerance) end print(isturnback(-180)) print(isturnback(90))
- 使用弧度作为角度的单位,那么只需要简单的修改一下常量的定义就可以了
local tolerance = 0.17 function isturnback(angle) -- 这一条语句实现了将任意范围的角度归一化到[0,2π)之间 angle = angle % (2*math.pi) return (math.abs(angle - math.pi) < tolerance) end print(isturnback(-180)) print(isturnback(90))
幂运算
- Lua也支持幂运算,使用符号^表示
- 像除法一样,幂运算的操作数和结果也永远是浮点类型(整型类型在幂运算时不能整除,例如,的结果不是整型值)
- 我们可以使用x^0.5来计算x的平方根,使用x^(1/3)来计算x的立方根
x = 4 x ^ 0.5 x = 27 x ^ (1 / 3)
六、关系运算
- Lua支持的关系运算符如下:
- 大于(>)、小于(<)
- 大于等于(>=)、小于等于(<=)
- 相等(==)
- 不相等(~=)
- Lua关系运算的结果都是boolean类型
- ==、~=说明:这两个运算符可以应用于任意两个值,当这两个值的类型不同时,Lua语言认为它们是不相等的;否则,会根据它们的类型再对两者进行比较
1 == 2
1 == 1
-- 类型不同, 直接返回false, 根本不进行比较
1 == "1"
"1" == "1"
"1" == "2"
- 比较数值时永远忽略数值的子类型,数值究竟是以整型还是浮点型表示并无区别,只与算术值有关(尽管如此,比较具有相同子类型的数值时效率更高)
1.0 == 1
1 == 1
1.1 == 1
七、数学库
- Lua语言提供了标准数学库math,由一组标准的数学函数组成,包括:
- 三角函数(sin、cos、tan、asin等):所有三角函数都以弧度为单位,并通过函数deg和rad进行角度和弧度的转换
- 指数函数
- 取整函数
- 最大函数max、最小函数min
- 用于生成伪随机的伪随机函数(random)
- 常量pi
- 常量huge(最大可表示数值,大多大叔平台上代表inf)
随机数发生器
- 函数math.random()用于生成伪随机数
- 一共有3中调用方式:
- 不带参数调用时:函数返回一个在[0,1)范围内的均匀分布的伪随机实数
- 当使用带有一个整型值n的参数调用时:函数返回一个在[1, n]范围内的伪随机整数
- 使用两个整型值l和u的参数调用时:函数返回在[l, u]范围内的伪随机整数
- 例如:
math.random() -- 可以模拟掷骰子的结果 math.random(6) math.random(10, 50)
- 函数randomseed()用于设置伪随机数发生器的种子,该函数的唯一参数就是数值类型的种子
- 在一个程序启动时,系统固定使用1为种子初始化伪随机发生器;如果不设置其他的种子,那么每次程序运行时都会生成相同的伪随机数序列
- 从调试的角度看,这是一个不错的特性,然而,对于一个游戏来说却会导致相同的场景重复不断的出现。为了解决这个问题,通常调用math.randomseed(os.time())来使用当前系统时间作为种子初始化随机数发生器(os.time()在后面文章介绍)
取整函数
- 数学库提供了三个取整函数:
- floor:向负无穷取整
- ceil:向正无穷取整
- modf:向零取整
- 当取整的结果能用整数表示时,返回结果为整型值,否则返回浮点型值
- modf除了返回取整后的值之外,还会返回小数部分作为第二个结果(Lua支持一个函数返回多个值)
- 例如:
math.floor(3.3) math.floor(-3.3) math.ceil(3.3) math.ceil(-3.3) math.modf(3.3) math.modf(-3.3) math.floor(2^70)
- 如果参数本身是一个整型值,那么它将原样返回
math.floor(1) math.floor(-1) math.floor(0) math.ceil(1) math.ceil(-1) math.ceil(0) math.modf(1) math.modf(-1) math.modf(0)
- 如果想将数值x向最近的整数取整,可以对x+0.5调用floor函数
- 不过,当参数是一个很大的整数时,简单的加法可能导致错误,考虑下面的代码
- +1.5的浮点值表示是不精确的,因为内部会以我们不可控制的方式取整
x = 2^52 + 1 print(string.format("%d %d", x, math.floor(x + 0.5)))
- 为了避免上面出现的问题,可以单独地处理整数值。例如:
function round(x) local f = math.floor(x) if x == f then return f else return math.floor(x + 0.5) end end
- 上面的函数总是会向上取整半个证书(例如2.5会被取整为3)。如果想进行无偏取整,即向距离最近的偶数取整半个整数,上述公布在x+0.5是奇数的情况下会产生不正确的结果
-- ok math.floor(3.5 + 0.5) -- wrong math.floor(2.5 + 0.5)
- 这时,还可以利用取整操作来解决上述公式中存在的问题:表达式(x%2.0==0.5)只有在x+0.5为奇数时(也就是我们的公式会出错的情况)为真。基于这些情况,定义一个无偏取整函数就很简单了:
function round(x) local f = math.floor(x) if(x == f) or (x % 2.0 == 0.5) then return f else return math.floor(x + 0.5) end end print(round(2.5)) print(round(3.5)) print(round(-2.5)) print(round(-1.5))
八、数值类型取值范围
整型取值范围
- 标准Lua使用64个比特位来存储整型值,其最大值为,约等于
- 数学库中的math.maxinteger和math.mininteger常量分别定义了整型值的最大值和最小值
回环:当数值很大或者很小发生溢出时,就会发生回环。回环的意思就是结果只能在maxinteger和mininteger之间,也就是对取模的算术结果。例如:
math.maxinteger + 1 == math.mininteger math.mininteger - 1 == math.maxinteger -math.mininteger == math.mininteger math.mininteger // -1 == math.mininteger
- 最大可以表示的整数是0x7fffffffffffffff(15个f),即处最高位(符号位,0位非负数值)外其余比特位均为1。当我们对0x7fffffffffffffff加1时,其结果就变为0x8000000000000000,即最小可表示的整数。最小整数比最大整数的表示幅度加1
math.maxinteger 0x7fffffffffffffff math.mininteger 0x8000000000000000
浮点类型取值范围
- 对于浮点数而言,标准Lua使用双精度。标准Lua使用64个比特位表示所有数值,其中11位为指数。双精度浮点数可以表示具有大致16个有效十进制位的数,范围从到
- 双精度浮点数对于大多数实际应用而言是足够大的,但是我们必须了解精度的限制。如果我们使用十位表示一个数,那么1/7会被取整到0.142857142。如果我们使用十位计算1/7*7,结果会是0.999999994而不是1。此外,用十进制表示的有限小数在用二进制表示时可能是无限小数。例如,12.7-20+7.3即便使用双精度表示也不是0,这是由于12.7和7.3的二进制表示不是有限小数
- 由于整型值和浮点型值的表示范围不同,因此当超过它们的表示范围时,整型值和浮点型值的算术运算会产生不同的结果:
math.maxinteger + 2
math.maxinteger + 2.0
- 上面的结果分析:
- 第一行对最大可表示整数进行了整型求和,结果发生了回环
- 第二行对最大可表示整数进行了浮点型求和,结果被取整成了一个近似值。这可以通过下面的比较运算证明
math.maxinteger + 2.0 == math.maxinteger + 1.0
- 尽管每一种表示方法都有其优势,但是只有浮点型才能表示小数。浮点型的值可以表示很大的范围,但是浮点型能够表示的整数范围被精确地限制在[, ]之间(不过这个范围已经很大了)。在这个范围内,我们基本可以忽略整型和浮点型的区别;超出这个范围后,我们则应该谨慎地思考所使用的表示方式
九、运算符优先级
- Lua中运算符的优先级如下所示(从高到低):
- 在二元运算符中,除了幂运算和连接操作是右结合的外,其他运算符都是左结合的。因此,下面各个表达式的左右两边等价
a + i < b/2+1 -- 等价于 (a+i) < ((b/2)+1)
5+x^2*8 -- 等价于 5+((x^2)*8)
a < y and y <= z -- 等价于 (a<y) and (y<=z)
-x^2 -- 等价于-(x^2)
x^y^z -- 等价于 x^(y^z)
- 当不确定某些表达式的运算符优先级时,应该显式地用括号来指定所希望的运算次序
十、整型值与浮点型值之间的转换
整数转浮点数
- 我们可以通过将整型值加上0.0将其转换为浮点型:
-3 + 0.0 0x7fffffffffffffff + 0.0
- 小于(即9007199254740992)的所有整型值的表示与双精度浮点型值的表示一样,对于绝对值超过了这个值的整型值而言,在将其强制转换为浮点型值时可能导致精度损失
9007199254740991 + 0.0 == 9007199254740991 9007199254740992 + 0.0 == 9007199254740992 -- 9007199254740992 + 1被取整为9007199254740992,因此不相等 9007199254740993 + 0.0 == 9007199254740993
浮点数转整数
- 通过与0进行按位或运算,可以把浮点型值转换为整型值
-- 浮点型值 2^53 -- 整型值 2^53 | 0
- 在将浮点数强制转换为整数时,Lua会检查数值是否与整型值表示完全一致(即没有小数部分且其值在整型值的表示范围内)。如果不满足条件则会抛出异常。例如
-- 有小数部分 3.2 | 0 -- 超出范围 2^64 | 0 -- 数值没有用整型表示 math.random(1, 3.5)
- 对小数取整必须显式地调用取整函数
- 另一种把浮点数转换为整型值的方式是使用math.tointeger(),该函数会在输入参数无法转换为整型值时返回nil:
math.tointeger(-258.0) math.tointeger(2^30) -- 不是整数值 math.tointeger(5.01) -- 超出范围 math.tointeger(2^64)
- 这个函数在需要检查一个数字能够被转换成整型值时尤其有用。例如,以下函数在可能时会将输入参数转换为整型值,否则保持原来的值不变:
function cond2int(x) return math.tointeger(x) or x end
十一、兼容性
- 请参阅《Lua程序设计》P27