Lua и IEEE754
В языке lua не предусмотрены штатные возможности работы с бинарными данными. Например, нет штатных возможностей записать в файл 8-байтный double float стандарта IEEE754 таким образом, как это можно сделать из того же языка Си. Необходимость работы с двоичной информацией возникает, например, при взаимодействии скрипта Lua с внешними программами с использованием уже существующих протоколов. Мне такое преобразование потребовалось для написания экспорта котировок из QUIK в SierraCharts.
Конечно, можно для этих целей написать расширение на С++. Однако делать расширение ради одной этой цели, и таскать ее в комплекте со скриптом на мой взгляд, совсем не комильфо. Все можно реализовать и на чистом lua - весь адаптер уместился в одном lua файле.
Lua позволяет писать в файл только строковые данные. Ну что же, давайте и будем создавать строки нужного формата, чтобы при записи получалось то же самое, что происходит при записи двоичных данных из более универсальных языков.
Как кодируется (в стандарте IEEE754) число с плавающей точкой 4-байтного формата (simple precision)?
Вот пример кодирования числа 0.15625. биты 0-23 задают мантиссу, следующие 8 бит - экспонента и бит знака. Экспонента задается со смещением 127. Подробнее в Википедии.
Как на lua преобразовать значение типа number в такое представление?
function float2str(x) local sign = 0
if x < 0 then
sign = 1;
x = -x
end
local mantissa, exponent = math.frexp(x)
if x == 0 then
mantissa = 0;
exponent = 0
else
mantissa = (mantissa * 2 - 1) * 8388608 -- math.ldexp(0.5, 24)
exponent = exponent + 126
end local v, byte = "" -- convert to bytes
x, byte = grab_byte(mantissa); v = v..byte -- 7:0
x, byte = grab_byte(x); v = v..byte -- 15:8
x, byte = grab_byte(exponent * 128 + x); v = v..byte -- 23:16
x, byte = grab_byte(sign * 128 + x); v = v..byte -- 31:24
return v
end
function grab_byte(v)
return math.floor(v / 256), string.char(math.floor(v) % 256)
end
Таким образом, функция принимает на вход number в терминах Lua, в на выходе выдает строку из 4 байтов. Эту строку можно, например, записать в файл файловой функцией write.
Для 8-битного представления (double precision в формате little-endian) можно воспользоваться следующей функцией:
function double2str(x)
local sign = 0
if x < 0 then
sign = 1;
x = -x
end
local mantissa, exponent = math.frexp(x)
if x == 0 then -- zero
mantissa, exponent = 0, 0
else
mantissa = (mantissa * 2 - 1) * 4503599627370496 -- math.ldexp(0.5, 53)
exponent = exponent + 1022
end
local v, byte = "" -- convert to bytes
x = mantissa
for i = 1,6 do
x, byte = grab_byte(x); v = v..byte -- 47:0
end
x, byte = grab_byte(exponent * 16 + x); v = v..byte -- 55:48
x, byte = grab_byte(sign * 128 + x); v = v..byte -- 63:56
return v
end
Unsigned long преобразуется совсем просто:
function ulong2str(value)
return string.char(value % 256,
math.floor(value/eval(2**8)) % 256,
math.floor(value/eval(2**16)) % 256,
math.floor(value/eval(2**24)))
end
В результате мы получаем 4 байта, описывающих unsigned long для архитектуры IBM.
Таким же образом можно делать преобразования для любых иных типов данных.
А как насчет обратного преобразования?
Тоже без проблем.
function convert_from_double(x)
local sign = 1
local mantissa = string.byte(x, 7) % 16
for i = 6, 1, -1 do
mantissa = mantissa * 256 + string.byte(x, i)
end
if string.byte(x, 8) > 127 then
sign = -1
end
local exponent = (string.byte(x, 8) % 128) * 16 + math.floor(string.byte(x, 7) / 16)
if exponent == 0 then
return 0
end
mantissa = (math.ldexp(mantissa, -52) + 1) * sign
return math.ldexp(mantissa, exponent - 1023)
end
Здесь мы преобразуем (строку 8 байт) double float в число lua. Для одинарной точности (4 байта) подойдет следующая функция:
function convert_from_single(x)
local sign = 1
local mantissa = string.byte(x, 3) % 128
for i = 2, 1, -1 do mantissa = mantissa * 256 + string.byte(x, i) end
if string.byte(x, 4) > 127 then sign = -1 end
local exponent = (string.byte(x, 4) % 128) * 2 + math.floor(string.byte(x, 3) / 128)
if exponent == 0 then return 0 end
mantissa = (math.ldexp(mantissa, -23) + 1) * sign
return math.ldexp(mantissa, exponent - 127)
end
не работает если входное значение меньше 10000000 и нет знаков после запятой
не работает с числами в диапазоне -1......+1
Убедиться в правильности работы этой и других функций можно скачав адаптер экспорта из квика в сиерру чартс. Там они работают уже много лет у массы людей.
RSS лента комментариев этой записи