大多数编程语言使用某些固定长度的比特位来表达数值。因此,数值的表示在范围和精度上都是有限制的。
标准Lua
使用 64
个比特位来存储整型值,其最大值为263-1,约等于1019;精简Lua
使用 32
个比特位存储整型值,其最大值约为 20亿
。数学库中的常量定义了整型值的最大值( math.maxinteger
)和最小值( math.mininteger
)。
64
位整型值中的最大值是一个很大的数值:全球财富总和(按美分计算)的数千倍和全球人口总数的数十亿倍。尽管这个数值很大,但是仍然有可能发生溢出。当我们在整型操作时出现比 mininteger
更小或者比 maxinteger
更大的数值时,结果就会回环。
在数学领域,回环的意思是结果只在 mininteger
和 maxinteger
之间,也就是对264取模的算数结果。在计算机领域,回环的意思是丢弃最高进位。假设最高进位存在,其将是 65
个比特位,代表264。因此,忽略第 65
个比特位不会改变值对264取模的结果。
在 Lua
语言中,这种行为对所有涉及整型值的算术运算都是一致且可预测的,如下所示:
> math.maxinteger + 1 == math.mininteger --> true > math.mininteger -1 == math.maxinteger --> true > -math.mininteger == math.mininteger --> true > math.mininteger // -1 == math.mininteger --> true
最大可以表示的整数是 0x7ff...fff
,即除最高位(符号位,零为非负数值)外其余比特位均为 1
。当我们对 0x7ff...fff
加 1
时,其结果变为 0x800...000
,即最小可表示的整数。最小整数比最大整数的表示幅度大 1
。
> math.maxinteger --> 9223372036854775807 > 0x7fffffffffffffff --> 9223372036854775807 > math.mininteger --> -9223372036854775808 > 0x8000000000000000 --> -9223372036854775808
对于浮点数而言,标准Lua使用双精度。标准Lua使用 64
个比特位表示所有数值,其中 11
位为指数。双精度浮点数可以表示具有大致 16
个有效十进制位的数,范围[-10308, 10308]。精简Lua使用 32
个比特位表示单精度浮点数,大致具有 7
个有效十进制位,范围[-1038, 1038]。
双精度浮点数对于大多数实际应用而言是足够大的,但是我们必须了解精度的限制。如果我们使用十位表示一个数,那么 1/7
会被取整到 0.142857142
。如果我们使用十位计算 1/7*7
,结果会是 0.999999994
,而不是 1
。此外,用十进制表示的有限小数在用二进制表示时可能是无限小数。例如, 12.7-20+7.3
即便是用双精度表示也不是 0
,这时由于 12.7
和 7.3
的二进制表示不是有限小数。
由于整型值和浮点型值的表示范围不同,因此当超过他们的表示范围时,整型值和浮点型值的算数运算会产生不同的结果:
> math.maxinteger + 2 --> -9223372036854775807 > math.maxinteger + 2.0 --> 9.2233720368548e+18
在上例中,两个结果从数学的角度看都是错误的,而且他们的错误方式不同。第一行对最大可表示整数进行了整型求和,结果发生了回环。第二行对最大可表示整数进行了浮点型求和,结果被取整成了一个近似值,这可以通过如下的比较运算证明:
> math.maxinteger + 2.0 == math.maxinteger + 1.0 --> true
尽管每一种表示都有其优势,但是只有浮点型才能表示小数。浮点型的值可以表示很大范围,但是浮点型能够表示的整数范围被精确地限制在[-253, 253]之间。在这个范围内,我们基本可以忽略整型和浮点型的区别;超出这个范围后,我们则应该谨慎地思考所使用的表示方式。