由于我使用的lua for windows是 5.1.5 版本所以很多 5.3 的特性并不能使用,比如说整型。
3.1 数值常量
我们可以用科学计数法书写数值常量,例如:
4
0.4
4.5e-3
0.3e12
5E+20
整型值和浮点型值类型都是number
> = type(3)
number
> = type(3.5)
number
> = type(3.0)
number
>
由于整型和浮点型都是number
类型,所以它们可以互相转换,具有相同算术值的整型值和浮点值在 Lua 中是相等的:
> = 3.0 == 3
true
> = 0.2e3 == 200
true
>
少数情况下需要区分整型和浮点型的时候可以使用math.type()
,由于我的 Lua 是低版本,所以并没有这个函数。
Lua 语言同样支持以0x
开头的十六进制常量,与其他语言不同,Lua 还支持十六进制浮点数,这种浮点数以 小数部分 和 以p
或'P'开头的指数部分组成。我的版本并没有这种特性。
0xff --> 225
0x1A3 --> 419
0x0.2 --> 0.125
0x1p-1 --> 0.5
0xa.bp2 --> 42.75
可以使用%a
参数,通过函数string.format()
对这种带p
的格式进行格式化输出,但我的版本并没有%a
的参数。
附 1:string.format 格式化参数
参数 | 含义 |
---|---|
%c | 接受一个数字,并将其转化为ASCII码表中对应的字符 |
%d, %i | 接受一个数字并将其转化为有符号的整数格式 |
%o | 接受一个数字并将其转化为八进制数格式 |
%u | 接受一个数字并将其转化为无符号整数格式 |
%x | 接受一个数字并将其转化为十六进制数格式,使用小写字母 |
%X | 接受一个数字并将其转化为十六进制数格式,使用大写字母 |
%e | 接受一个数字并将其转化为科学计数法格式,使用小写字母e |
%E | 接受一个数字并将其转化为科学计数法格式,使用大写字母E |
%f | 接受一个数字并将其转化为浮点数格式 |
%g(%G) | 接受一个数字并将其转化为%e(%E,对应%G)即%f中较短的一种格式 |
%q | 接受一个字符串并将其转化为可安全被 Lua 编译器读入的格式 |
%s | 接受一个字符串并按照给定的参数格式化该字符串 |
为了进一步细化格式,可以在%后添加参数将以如下的顺序读入:
- 符号:一个
+
号表示其后的数字转义符将让正数显示正号,默认情况下只有负数才显示符号。 - 占位符:一个
0
,在后面制定了字串宽度时展位用,不填默认用空格占位。 - 对齐表示:在指定了字串宽度时,默认右对齐,增加一个
-
号可以改为左对齐。 - 宽度数值
- 小数位数/字串裁切:在宽度数值后增加的小数部分
n
,若后接f则设定该浮点数只保留n位,若后接s则设定该字符串只显示前n位。
> = string.format("%02d:%02d:%02d",os.date("*t").hour, os.date("*t").min, os.date("*t").sec)
11:38:06
>
3.2 算术运算
除了加减乘除运算外,Lua 语言还支持取整除法、取模和指数运算。
对于 Lua 5.3 引入的整型而言,主要的建议就是 “开发人员要么选择忽略整型和浮点型二者之间的不同,要么就完整地控制每一个数值表示。” 因此,不论时操作整型值还是浮点型值,结果都应该是一样的。
除除法外,任意两个整型运算结果应仍是整型,两个浮点型运算结果仍是浮点型,当有一个操作数是整型另一个是浮点型时,Lua 会先将整型转化为浮点型。
由于两个整数相除的结果不总是整数,所以除法运算的操作数永远会被转换为浮点型。
Lua 5.3针对整数除法引入了一个称为 floor 除法的新算术运算符//
。顾名思义,floor除法会对得到的商向负无穷取整,从而保证结果是一个整数。这样,floor 除法就可以跟其他算术运算符遵守一样的规则,当两个操作数都是整数的时候,结果就是整型值,否则就是浮点型值。
以下公式是取模运算的定义:
a % b == a - ((a // b) * b)
如果操作数是整数,那么取模运算的结果也是整型值,否则是浮点型值。
取整的结果永远与第二个操作数的符号保持一致,对于任意指定的正常量K,不论x的正负,表达式 x % K
的结果永远在[0,k-1]或者[k-1,0]之间(取决于K的正负):
> = 1 % -3
-2
> = 2 % -3
-1
> = 3 % -3
0
> = 4 % -3
-2
>
x - x%0.01
恰好是保留两位小数的结果,x-x%0.001
恰好是保留三位小数的结果,以此类推:
> x = 3.33333
> = x - x% 0.01
3.33
> = x- x%0.001
3.333
>
因此,x-x%1
是获取整数部分,x%1
是获取小数部分。
Lua 同样支持幂运算,使用符号^
表示即可,我们可以用x^0.5
来进行开方操作。
3.3 关系运算
Lua 语言提供了下列关系运算:
< > <= >= ~=
这些关系运算的结果都是Boolean
类型。
==
和~=
进行相等性判断,如果两个操作数不是同一个类型,Lua 语言会认为他们是不相等的(整型和浮点型都是number
型):
> = 0.3 == "0.3"
false
> = 3.0 == 3
true
>
3.4 数学库
Lua 语言提供了数学库math
。标准数学库由一组标准的数学函数组成,包括三角函数、指数函数、取整函数、最大和最小、随机数函数以及常量pi
和huge
,huge指的是最大的可表示数。
所有的三角函数都以弧度为单位,并通过函数deg和rad进行角度和弧度的转换。
3.4.1 随机数发生器
函数math.random
用于生成伪随机数,共有三种调用方式。不带参数时,该函数将返回一个在 [0, 1) 范围内均匀分布的伪随机实数。当使用一个带有整型值 n 的参数调用时,该函数将返回一个在 [1, n] 范围内的伪随机整数。当使用两个整型值 l 和 u ,该函数返回在 [l, u] 范围内的随机整数。
函数randomseed
用于设置伪随机数发生器的种子,该函数的唯一参数就是数值类型的种子。在一个程序启动时,系统固定使用1为种子初始化伪随机数发生器。如果不设置其他的种子,每次程序运行时都会生成相同的随机数序列。所以通常用math.randomseed(os.time())
来使用当前系统时间作为种子初始化随机数发生器。
3.4.2 取整函数
数学库提供了三个取整函数:floor
、ceil
和modf
。其中floor
向负无穷取整,ceil
向正无穷取整,modf
向零取整。取整结果能用整型表示时,返回结果为整型值,否则返回浮点型值(用浮点数表示的整数值),除了返回取整后的值以外,函数modf
还会返回小数部分作为第二结果:
> = math.floor(3.3)
3
> = math.floor(-3.3)
-4
> = math.ceil(3.3)
4
> = math.ceil(-3.3)
-3
> = math.modf(3.3)
3 0.3
> = math.modf(-3.3)
-3 -0.3
> = math.floor(2^70)
1.1805916207174e+021
>
如果参数本身就是一个整型值,那么就会返回它本身。
如果想要将数值 x 向最近的整数取整,可以对 x + 0.5 调用floor
函数。
function round(x)
local f = math.floor(x)
if x == f then return f
else return math.floor(x + 0.5)
end
end
3.5 表示范围
大多数编程语言使用某些固定长度的比特位来表达数值。因此,数值的表示范围和精度上都是有限制的。
标准 Lua 使用 64 个比特位来存储整型值,其最大值为263-1,约等于1019;精简 Lua 使用 32 个比特位,存储整型值,其最大值约为20亿,数学库中定义了整型值的最大值math.maxinteger
和最小值math.mininteger
。
当我们在整型操作时出现比mininteger
更小或者maxinteger
更大二点数值时,就会出现 回环。
由于我的版本较低,数学库中并没有这两个常量,所以无法在 lua 环境下进行测试
math.maxinteger + 1 == math.mininteger
math.mininteger - 1 == math.maxinteger
对于浮点数而言,标准 Lua 使用双精度。标准 Lua 使用 64 个比特位表示所有数值,其中11位为指数。双精浮点数可以表示具有大致16个有效十进制位的数,范围从 -10^308 到 10^308。精简 Lua 使用32个比特位表示单精度浮点数,可以表示具有大致7个有效十进制位,范围从 -10^38 到 10^38。
3.6 惯例
我们可以简单地通过增加 0.0 的方式将整型值转换为浮点型值(我的版本中并没有这样的效果)。
-3 + 0.0 --> -3.0
0x7fffffffffffffff +0.0 --> 9.2233720368548e+18
小于 2^53 的所有整型值的表示与双精度浮点型值的表示一样,对于绝对值超过了这个值的整型值而言,再将其转换为浮点型值时可能导致精度丢失:
9007199254740992 + 0.0 == 9007199254740992 --> true
9007199254740993 + 0.0 == 9007199254740993 --> false
通过与零进行按位或运算,可以把浮点型值强制转换为整型值(需要 Lua 5.3),但是小数不能与零进行按位或运算:
print(2^53) --> 9.007199254741e+15
print(2^53 | 0) --> 9007199254740992
print(3.3 | 0) --> 报错
print(2^64 | 0) --> 超出范围
因此对于小数必须显式调用取整函数floor
、ceil
或 modf
。
3.7运算符优先级
在第一章 Lua 语言入门 习题 1.7 中已经介绍了运算符优先级,此处不再赘述。
在二元运算符中,除了幂运算和连接操作符是右结合外,其余运算符都是左结合。所谓右结合就是从右向左执行运算,例如:x^y^z
等价于x^(y^z)
,而x+y+z
等价于(x+y)+z
。
在不能确定某些表达式的运算符优先级的时候,应该显式地使用括号来指定顺序。
3.8 兼容性
Lua 5.3 引入的整型值导致其相对于此前的 Lua 版本出现了一定的不兼容,但是如前所述,程序员基本上可以忽略整型值和浮点型值之间的不同。
Lua 5.2 和 Lua 5.3 之间最大的不同是整数的表示范围。Lua 5.2 支持的最大整数为 2^53,而 Lua 5.3 支持的最大整数为 2^63。在当作计数值使用时,它们之间的区别通常不会导致问题;然而,当把整型值当作通用的比特位使用时(例如,把 3 个 20-bit 的整型值放在一起使用),他们之间的区别则可能很重要。
虽然 Lua 5.2 不支持整型,但是几个场景下仍然会涉及整型问题。例如,C 语言实现的库函数通常使用整型参数,但 Lua 5.2 却并没有约定这些情况下浮点型值和整型值之间的转换方法:Lua 5.2 可能将 -3.2 转换为 -3,也可能转换成 -4。Lua 5.3则明确了这种转换规则。
由于 Lua 5.2 中的数值类型只有一种,所以并没有提供函数math.type
。由于 Lua 5.2 中不存在整型的概念,所以也没有常量math.maxinteger
及math.mininteger
。虽然可以实现,但 Lua 5.2 中也没有floor
除法。
可能让人感到震惊的是,与整型引入相关的问题根源在于,Lua 语言将数值转换为字符串的方式。Lua 5.2将所有的整数值格式化为整型(即不带小数点),而 Lua 5.3则将所有的浮点数格式化为浮点型(带有十进制小数点或指数)。因此 Lua 5.2 会将 7.0 格式化为 ”7“ 输出而 Lua 5.3 则会将其格式化为 ”7.0“输出。虽然 Lua 语言从未说明过格式化数值的方式,但是很多程序员都默认的是早期版本的格式化输出行为。在将字符串转换为字符串时,我们可以通过显式的指明格式的方式来避免这种问题。
3.9 练习
- 练习 3.1:以下哪些时有效的数值常量?它们的值分别是多少?
.0e12 .e12 0.0e 0x12 0xABFG 0xA FFFF 0xFFFFFFFF
0x 0x1P10 0.1e1 0x0.1p1
.0e12 --> 0
.e12 --> 报错
0.0e --> 报错
0x12 --> 18
0xABFG --> 报错(十六进制没有G)
0xA --> 10
FFFF --> 报错(十六进制需要加上0x)
0xFFFFFFFF --> 4294967295
0x --> 报错
0x1P10 --> 1024
0.1e1 --> 1
0x0.1p1 --> 报错
- 练习 3.2:解释下列表达式之所以得出相应结果的原因。(注意:证书算术运算总是会回环。)
math.maxinteger * 2 --> -2
math.mininteger * 2 --> 0
math.maxinteger * math.maxinteger --> 1
math.mininteger * math.mininteger --> 0
math.maxinteger的二进制为除符号位外全1,乘以2相当于整体左移1位,最后两位为10,1被移到符号位,有符号数用补码表示,取反+1后为全零,最后两位10,符号位不变,结果是-2。
math.mininteger的二进制为除首位外全0,左移1位后首位被抛弃,结果是全0,取反加1仍为全0,所以结果是0。
math.maxinteger除首位外全1,相乘后得到除最后一位和首部外,中间全0,超过符号位以外的部分被抛弃,其余全为0,最后只剩最后一位的1,所以结果为1。
math.mininteger除首位外全0,超出符号位以外的数值均被抛弃,其余全为0,所以结果为0。
- 练习 3.3:下列代码的输出结果是是什么?
for i = -10, 10 do
print(i, i % 3)
end
> for i = -10, 10 do
>> print(i, i % 3)
>> end
-10 2
-9 0
-8 1
-7 2
-6 0
-5 1
-4 2
-3 0
-2 1
-1 2
0 0
1 1
2 2
3 0
4 1
5 2
6 0
7 1
8 2
9 0
10 1
>
可以考虑从0开始往下倒,就可以更方便的分析负数取余的结果。
- 练习 3.4:表达式2^ 3^ 4的值是什么?表达式2^ -3^ 4呢?
> = 2^3^4
2.4178516392293e+024
> = 2^-3^4
4.1359030627651e-025
>
由于幂运算是右结合且优先级高于负号,所以均是从右往左运算。
- 练习 3.5:当分母是 10 的整数次幂时,数值 12.7 与表达式 127/10相等。能否认为当分母是2的整数次幂时,这是一种通用规律?对于数值5.5情况又会怎样呢?
不懂
- 练习3.6:请编写一个通过高、母线与轴线的夹角来计算正圆锥体体积的函数。
圆锥体体积的公式是V = 1/3 Sh 其中S为底面圆面积,h为高。轴线就是指圆锥的高。S = * r^2,假设夹角为,结果为 V = 1/3 * * tan^2 h^3
function ConeVolume(height, deg) --给出高和夹角的角度值
local rad = math.rad(deg)
local volume = 1/3 * math.pi * (math.tan(rad))^2 * height^3
return volume
end
- 练习 3.7:利用函数
math.random()
写一个生成遵循正态分布的伪随机数发生器。
-- 标准正态分布随机数发生器
function StandardNormalDistribution(floor, ceil) --给出范围
local prob = 0 --f(x)
local mean = 0 --数学期望
local standardDeviation = 1 --标准差
local x = 0
local y = 0
math.randomseed(os.time())
repeat
y = math.random()
x = floor + (ceil - floor) * math.random()
prob = 1/((2 * math.pi * standardDeviation^2)^0.5) * math.exp(-( x - mean)^2 / (2 * standardDeviation^2))
until prob < y
return x
end