整型表示中使用一个 比特
来存储符号位。因此, 64
位整型数最大可以表示 263-1
而不是 264-1
。通常,这点区别并无关紧要,因为 263-1
已经很大了。不过,由于我们可能需要处理使用无符号整型表示的外部数据或实现一些需要 64
位整型数的算法,因而有时也不能浪费这个符号位。此外,在精简 Lua
中,这种区别可能会很重要。例如,如果用一个 32
位有符号整型数表示文件中的位置,那么能够操作的最大文件大小就是 2GB
;而一个无符号整型能操作的最大文件大小则是有符号整型数的 2
倍,即 4GB
。
Lua
语言不显式支持无符号整型数。不过尽管如此,只要稍加注意,在 Lua
语言中处理无符号整型数并不难。
虽然看上去不太友好,但可以直接写出比 263-1
大的常量:
> x = 13835058055282163712 -- 3 << 62 > x --> -4611686018427387904
这里的问题并不在于常量本身,而在于 Lua
语言输出常量的方式;默认情况下,打印数值时是将其作为有符号整型数进行处理的。我们可以使用选项 %u
或 %x
在函数 string.format
中指定以无符号整型数进行输出:
> string.format("%u", x) --> 13835058055282163712 > string.format("0x%X", x) --> 0xC0000000000000000
根据有符号整型数的表示方式( 2
的补码),加法、减法和乘法操作对于有符号整型数和无符号整型数是一样的:
> string.format("%u", x) --> 13835058055282163712 > string.format("%u", x + 1) --> 13835058055282163713 > string.format("%u", x - 1) --> 13835058055282163711
提示
对于这么大的数,即便 x
乘以 2
也会溢出,所以示例中没有演示乘法。
关系运算对于有符号整型数和无符号整型数是不一样的,当比较具有不同符号位的整型数时就会出现问题。对于有符号整型数而言,符号位被置位的整数更小,因为它代表的是负数:
> 0x7fffffffffffffff < 0x8000000000000000 --> false
如果把这两个整数都当做无符号的,那么结果显然是不正确的。因此,我们需要一种不同的操作来比较无符号整型数。 Lua5.3
提供了函数 math.ult
(unsigned less than)来完成这个需求:
> math.ult(0x7fffffffffffffff < 0x8000000000000000) --> true
另一种方法是在进行有符号比较前先用掩码掩去两个操作数的符号位:
> mask = 0x8000000000000000 > (0x7fffffffffffffff ~ mask) < (0x8000000000000000 ~ mask) --> true点击复制复制失败已复制
无符号除法和有符号除法也不一样,如下所示给出了一种无符号除法的算法:
function udiv(n, d) if d < 0 then if math.ult(n, d) then return 0 else return 1 end end local q = ((n >> 1) // d) << 1 local r = n - q * d if not math.ult(r, d) then q = q + 1 end return q end
第一个比较( d < 0
)等价于比较 d
是否大于 263
。如果大于,那么商只能是 1
(如果 n
等于或大于 d
)或 0
。否则,我们是被除数除以 2
,然后除以整数,再把结果乘以 2
。右移一位等价于除以 2
的无符号除法,其结果是一个非负有符号整型数。后续的左移则纠正了商,还原了之前的除法。
总体上说, floor(floor(n/2)/d)*2
(算法进行的计算)与 floor(((n/2)/d)*2)
(正确的结果)并不等价。不过,要证明他们之间最多相差 1
并不困难。因此,算法计算了余数(变量 r
),然后判断余数是否比除数大,如果余数比除数大则纠正商(加一)即可。
无符号整型数和浮点数之间的转换需要进行一些调整。要把一个无符号整型数转换为浮点型数,可以先将其转换成有符号整型数,然后通过取模运算纠正结果:
> u = 11529215046068469760 -- 一个示例 > f = (u + 0.0) % 2^64 > string.format("%.0f", f) --> 11529215046068469760
由于标准转换把 u
当做有符号整型数,因此表达式 u+0.0
的值是 -6917529027641081856
,而之后的取模操作会把这个值限制在有符号整型数的表示范围内(在实际的代码中,由于涉及浮点型数的取模运算肯定会进行类型转换,所以并不需要进行这次加法运算)。
要把一个浮点型数转换为无符号整型数,可以使用如下的代码:
> f = 0xA000000000000000.0 -- 一个示例 > u = math.tointeger(((f + 2^63) % 2^64) - 2^63) > string.format("%x", u) --> a000000000000000点击复制复制失败已复制
加法把一个大于 263
的数转换为一个大于 264
的数,取模运算把这个数限制到 [0, 263)
范围内,然后通过减法把结果变成一个“负”值(即最高位置位的值)。对于小于 263
的值,加法结果小于 264
,所以取模运算没有任何效果,之后的减法则把它恢复到了之前的值。