2015-04-04

Lua把数字以二进制方式写入文件并读取

我在用Lua实现自己的数据库时,有个需求是把数字以二进制的方式写入文件并读取,因为转换成文本即浪费储存空间,又不高效。 但是lua 5.1没有原生的方法来实现此需求,也没有原生的位操作(Luajit有)。5.3原生Lua有了string.pack,但碍于还没有luajit暂不考虑。

虽然也可以自己用c写一个库,更完美和高效,但这种小东西能用纯lua实现的话最好,毕竟不同的设备编译一下c代码挺烦的。 简单第一灵活第二,用纯lua实现就两样都占了。

那为什么不用Luajit的位操作?因为不一定所有时候都能用Luajit,还是必须考虑灵活性。

网上搜了下看看有没有人已经做了,结果不是不全就是有各种bug,因此自己改写了下,支持带符号(负数)的整形,以及double浮点数。 其他程序如C也可以直接读取到内存,不过要注意下字节顺序。

-- packer.lua
-- by FullAutoCapitalism

local string_char = string.char
local string_byte = string.byte
local assert = assert
local ipairs = ipairs
local math_huge = math.huge
local math_ldexp = math.ldexp
local math_floor = math.floor
local math_frexp = math.frexp

local INT_SIZE = 6
local DOUBLE_SIZE = 8
local _M = {
    INT_SIZE = INT_SIZE,
    DOUBLE_SIZE = DOUBLE_SIZE
}

--Conversion lua number to signed int48 binary byte
--return 6 chars (48bits) because double Integer precision only 52bits
local function pack_int(n)
    return string_char(
          --0,  如果需要填充位数可以打开
          --0,
          math_floor(n / 10995116277760) % 0x100, --x10000000000 Because Lua does not support more
          math_floor(n / 4294967296) % 0x100,     --than 32 hex digits (eg 0x100000000), use decimal.
          math_floor(n / 0x1000000) % 0x100,      --今天你进坑了么?[doge face]
          math_floor(n / 0x10000) % 0x100,
          math_floor(n / 0x100) % 0x100,
          n % 0x100)
end

---Conversion signed int48 binary format to lua number
local function unpack_int(b1,b2,b3,b4,b5,b6)
    local x = 0
    x = x*0x100 + b1
    x = x*0x100 + b2
    x = x*0x100 + b3
    x = x*0x100 + b4
    x = x*0x100 + b5
    x = x*0x100 + b6
    if b1 > 127 then --If it is negative
      return x - 281474976710656
    end
    return x
end
---read signed int48 binary format from file
local function read_int_from_file(file)
    local b = file:read(INT_SIZE)
    if not b then return nil end
    return unpack_int(string_byte(b, 1, 6))
end

--Conversion lua number to double binary byte
local function pack_double(n)
    local sign = 0
    if n < 0.0 then
        sign = 0x80
        n = -n
    end
    local mant, expo = math_frexp(n)
    if mant ~= mant then
        return string_char(0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)  -- nan
    elseif mant == math_huge then
        if sign == 0 then
            return string_char(0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)-- inf
        else
            return string_char(0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)-- -inf
        end
    elseif mant == 0.0 and expo == 0 then
        return string_char(sign, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)-- zero
    else
        expo = expo + 0x3FE
        mant = (mant * 2.0 - 1.0) * math_ldexp(0.5, 53)
        return string_char( sign + math_floor(expo / 0x10),
                             (expo % 0x10) * 0x10 + math_floor(mant / 281474976710656),
                             math_floor(mant / 1099511627776) % 0x100,
                             math_floor(mant / 4294967296) % 0x100,
                             math_floor(mant / 0x1000000) % 0x100,
                             math_floor(mant / 0x10000) % 0x100,
                             math_floor(mant / 0x100) % 0x100,
                             mant % 0x100)
    end
end

--Conversion double binary byte to lua number
local function unpack_double(b1, b2, b3, b4, b5, b6, b7, b8)
    local sign = b1 > 0x7F
    local expo = (b1 % 0x80) * 0x10 + math_floor(b2 / 0x10)
    local mant = ((((((b2 % 0x10) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
    if sign then
        sign = -1
    else
        sign = 1
    end
    local n
    if mant == 0 and expo == 0 then
        n = sign * 0.0
    elseif expo == 0x7FF then
        if mant == 0 then
            n = sign * math_huge
        else
            n = 0.0/0.0
        end
    else
        n = sign * math_ldexp(1.0 + mant / 4503599627370496.0, expo - 0x3FF)
    end
    return n
end
local function read_double(file)
    local b = file:read(DOUBLE_SIZE)
    return unpack_double(string_byte(b, 1, DOUBLE_SIZE))
end

_M.pack_int = pack_int
_M.unpack_int  = unpack_int

_M.read_int_from_file  = read_int_from_file

_M.pack_double = pack_double
_M.unpack_double  = unpack_double

return _M


回主页