--- read and write (little endian) binary. -- This is apart of the [LEEF-filesystem](https://github.com/Luanti-Extended-Engine-Features/LEEF-filesystem) module. --@module binary local assert, math_huge, math_frexp, math_floor = assert, math.huge, math.frexp, math.floor local negative_nan = 0/0 local positive_nan = negative_nan ^ 1 --- reading binary inputs. -- read a binary inputs using a `read_byte` function. -- @section reading -- @see read_byte --- expected function inputs. -- functions will expect either a `read_byte` or `write_byte` function as inputs -- @section input --- `read_byte` is a param name which refers to a function which reads the next byte- returning a whole number between 0-255. -- -- function byte() -- left = left - 1 -- return assert(file_handle:read(1):byte()) -- --reads the next chracter, and converts it to a "numerical code" using string.byte() -- --it's important that this function moves forward in the file stream (as :read(1) does) -- end -- @function read_byte -- @return a bytecode (an int between 0 and 255.) --- `write_byte` is similar to read_byte, however it is given an input and expected to write it to the file. -- (example needed) -- @function write_byte --- functions -- @section functions --- read an IEEE 754 single precision (32-bit) floating point number -- @function read_single -- @param function @{read_byte} -- @return number function leef.binary.read_single(read_byte) -- First read the mantissa local mantissa = read_byte() / 0x100 mantissa = (mantissa + read_byte()) / 0x100 -- Second and first byte in big endian: last bit of exponent + 7 bits of mantissa, sign bit + 7 bits of exponent local exponent_byte = read_byte() local sign_byte = read_byte() local sign = 1 if sign_byte >= 0x80 then sign = -1 sign_byte = sign_byte - 0x80 end local exponent = sign_byte * 2 if exponent_byte >= 0x80 then exponent = exponent + 1 exponent_byte = exponent_byte - 0x80 end mantissa = (mantissa + exponent_byte) / 0x80 if exponent == 0xFF then if mantissa == 0 then return sign * math_huge end -- Differentiating quiet and signalling nan is not possible in Lua, hence we don't have to do it return sign == 1 and positive_nan or negative_nan end assert(mantissa < 1) if exponent == 0 then -- subnormal value return sign * 2^-126 * mantissa end return sign * 2 ^ (exponent - 127) * (1 + mantissa) end --- read an IEEE 754 double-precision (64-bit) floating point number -- @function read_double -- @param function @{read_byte} -- @return number function leef.binary.read_double(read_byte) -- First read the mantissa local mantissa = 0 for _ = 1, 6 do mantissa = (mantissa + read_byte()) / 0x100 end -- Second and first byte in big endian: last 4 bits of exponent + 4 bits of mantissa; sign bit + 7 bits of exponent local exponent_byte = read_byte() local sign_byte = read_byte() local sign = 1 if sign_byte >= 0x80 then sign = -1 sign_byte = sign_byte - 0x80 end local exponent = sign_byte * 0x10 local mantissa_bits = exponent_byte % 0x10 exponent = exponent + (exponent_byte - mantissa_bits) / 0x10 mantissa = (mantissa + mantissa_bits) / 0x10 if exponent == 0x7FF then if mantissa == 0 then return sign * math_huge end -- Differentiating quiet and signalling nan is not possible in Lua, hence we don't have to do it return sign == 1 and positive_nan or negative_nan end assert(mantissa < 1) if exponent == 0 then -- subnormal value return sign * 2^-1022 * mantissa end return sign * 2 ^ (exponent - 1023) * (1 + mantissa) end --- read an unsigned integer of any given length -- @function read_uint -- @param function @{read_byte} -- @param int length in bytes of unsigned integer -- @return unit number function leef.binary.read_uint(read_byte, bytes) local factor = 1 local uint = 0 for _ = 1, bytes do uint = uint + read_byte() * factor factor = factor * 0x100 end return uint end --- read a signed integer of any given length -- @function read_uint -- @param function @{read_byte} -- @param int length in bytes of integer -- @return int number function leef.binary.read_int(read_byte, bytes) local uint = leef.binary.read_uint(read_byte, bytes) local max = 0x100 ^ bytes if uint >= max / 2 then return uint - max end return uint end --- writing binary -- @function write_uint -- @param write_byte @{write_byte} -- @tparam int unit unit to write -- @tparam int bytes number of bytes to right function leef.binary.write_uint(write_byte, uint, bytes) for _ = 1, bytes do write_byte(uint % 0x100) uint = math_floor(uint / 0x100) end assert(uint == 0) end function leef.binary.write_int(write_byte, int, bytes) local max = 0x100 ^ bytes if int < 0 then assert(-int <= max / 2) int = max + int else assert(int < max / 2) end return leef.binary.write_uint(write_byte, int, bytes) end function leef.binary.write_single(write_byte, number) if number ~= number then -- nan: all ones for _ = 1, 4 do write_byte(0xFF) end return end local sign_byte, exponent_byte, mantissa_byte_1, mantissa_byte_2 local sign_bit = 0 if number < 0 then number = -number sign_bit = 0x80 end if number == math_huge then -- inf: exponent = all 1, mantissa = all 0 sign_byte, exponent_byte, mantissa_byte_1, mantissa_byte_2 = sign_bit + 0x7F, 0x80, 0, 0 else -- real number local mantissa, exponent = math_frexp(number) if exponent <= -126 or number == 0 then -- must write a subnormal number mantissa = mantissa * 2 ^ (exponent + 126) exponent = 0 else -- normal numbers are stored as 1. mantissa = mantissa * 2 - 1 exponent = exponent - 1 + 127 -- mantissa << 1 <=> exponent-- assert(exponent < 0xFF) end local exp_lowest_bit = exponent % 2 sign_byte = sign_bit + (exponent - exp_lowest_bit) / 2 mantissa = mantissa * 0x80 exponent_byte = exp_lowest_bit * 0x80 + math_floor(mantissa) mantissa = mantissa % 1 mantissa = mantissa * 0x100 mantissa_byte_1 = math_floor(mantissa) mantissa = mantissa % 1 mantissa = mantissa * 0x100 mantissa_byte_2 = math_floor(mantissa) mantissa = mantissa % 1 assert(mantissa == 0) -- no truncation allowed: round numbers properly using modlib.math.fround end write_byte(mantissa_byte_2) write_byte(mantissa_byte_1) write_byte(exponent_byte) write_byte(sign_byte) end function leef.binary.write_double(write_byte, number) if number ~= number then -- nan: all ones for _ = 1, 8 do write_byte(0xFF) end return end local sign_byte, exponent_byte, mantissa_bytes local sign_bit = 0 if number < 0 then number = -number sign_bit = 0x80 end if number == math_huge then -- inf: exponent = all 1, mantissa = all 0 sign_byte, exponent_byte, mantissa_bytes = sign_bit + 0x7F, 0xF0, {0, 0, 0, 0, 0, 0} else -- real number local mantissa, exponent = math_frexp(number) if exponent <= -1022 or number == 0 then -- must write a subnormal number mantissa = mantissa * 2 ^ (exponent + 1022) exponent = 0 else -- normal numbers are stored as 1. mantissa = mantissa * 2 - 1 exponent = exponent - 1 + 1023 -- mantissa << 1 <=> exponent-- assert(exponent < 0x7FF) end local exp_low_nibble = exponent % 0x10 sign_byte = sign_bit + (exponent - exp_low_nibble) / 0x10 mantissa = mantissa * 0x10 exponent_byte = exp_low_nibble * 0x10 + math_floor(mantissa) mantissa = mantissa % 1 mantissa_bytes = {} for i = 1, 6 do mantissa = mantissa * 0x100 mantissa_bytes[i] = math_floor(mantissa) mantissa = mantissa % 1 end assert(mantissa == 0) end for i = 6, 1, -1 do write_byte(mantissa_bytes[i]) end write_byte(exponent_byte) write_byte(sign_byte) end function leef.binary.write_float(write_byte, number, double) (double and leef.binary.write_double or leef.binary.write_single)(write_byte, number) end --- misc binary helpers -- @section misc --- "returns nearest 32-bit single precision float representation of a number" (or something) -- @function fround() -- @param number -- @return nearest 32-bit single precision float representation of a number function leef.binary.fround(number) if number == 0 or number ~= number then return number end local sign = 1 if number < 0 then sign = -1 number = -number end local _, exp = math.frexp(number) exp = exp - 1 -- we want 2^exponent >= number > 2^(exponent-1) local powexp = 2 ^ math.max(-126, math.min(exp, 127)) local leading = exp <= -127 and 0 or 1 -- subnormal number? local mantissa = math.floor((number / powexp - leading) * 0x800000 + 0.5) if mantissa > 0x800000 -- doesn't fit in mantissa or (exp >= 127 and mantissa == 0x800000) -- fits if the exponent can be increased then return sign * inf end return sign * powexp * (leading + mantissa / 0x800000) end