Lua и IEEE754

Опубликовано в QLua

В языке lua не предусмотрены штатные возможности работы с бинарными данными. Например, нет штатных возможностей записать в файл 8-байтный double float стандарта IEEE754 таким образом, как это можно сделать из того же языка Си. Необходимость работы с двоичной информацией возникает, например, при взаимодействии скрипта Lua с внешними программами с использованием уже существующих протоколов. Мне такое преобразование потребовалось для написания экспорта котировок из QUIK в SierraCharts.

Конечно, можно для этих целей написать расширение на С++. Однако делать расширение ради одной этой цели, и таскать ее в комплекте со скриптом на мой взгляд, совсем не комильфо. Все можно реализовать и на чистом lua - весь адаптер уместился в одном lua файле.

Lua позволяет писать в файл только строковые данные. Ну что же, давайте и будем создавать строки нужного формата, чтобы при записи получалось то же самое, что происходит при записи двоичных данных из более универсальных языков.

 

Как кодируется (в стандарте IEEE754) число с плавающей точкой 4-байтного формата (simple precision)?

float 4 bytes

Вот пример кодирования числа 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) можно воспользоваться следующей функцией:

 double float IEEE-754

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

 

Комментарии   

# sergei200602 18.01.2019 14:12
function float2str(x)

не работает если входное значение меньше 10000000 и нет знаков после запятой

не работает с числами в диапазоне -1......+1
# admin 18.01.2019 16:07
Ищите ошибку. Что то вы не замечаете.

Убедиться в правильности работы этой и других функций можно скачав адаптер экспорта из квика в сиерру чартс. Там они работают уже много лет у массы людей.

Недостаточно прав для комментирования

Архив QLua