在纯Lua中解析IEEE754双精度浮点数?

发布于 2025-01-02 15:23:11 字数 665 浏览 2 评论 0原文

我有以 IEEE754 格式编码的固定大小的双精度数组,任何人都可以向我指出任何可以执行相关操作的 Lua 代码吗?

更新:我无法发布这个问题,因为它太短了,所以这里是我在解决这个问题的过程中编写的一些代码 - 这会将二进制字符串转换为像 "0011000"< 这样的位字符串/代码>

-- get string of bits for given byte
function byte2bits(i)
   local result=""
   for c=1,8 do
      nextByte = i % 2
      i = (i - nextByte)/2
      result = result .. nextByte
   end
   return string.reverse(result)
end

-- get a string of bits from string of bytes
function str2bits(s)
   result=''
   for i = 1, string.len(s) do
      --print(string.byte(s, i))
      result=result .. byte2bits(string.byte(s,i))
   end
   return result
end

I have fixed size array of doubles encoded in IEEE754 format, can anyone point me to any Lua code that can do something related?

Update: I can't post this question because it's too short, so here's some code I wrote in process of figuring this out — this converts binary string into string of bits like "0011000"

-- get string of bits for given byte
function byte2bits(i)
   local result=""
   for c=1,8 do
      nextByte = i % 2
      i = (i - nextByte)/2
      result = result .. nextByte
   end
   return string.reverse(result)
end

-- get a string of bits from string of bytes
function str2bits(s)
   result=''
   for i = 1, string.len(s) do
      --print(string.byte(s, i))
      result=result .. byte2bits(string.byte(s,i))
   end
   return result
end

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

一枫情书 2025-01-09 15:23:11

我实际上需要这样做并来这里寻求答案。看起来以前没有人这样做过,所以我最终自己做了一些东西。我没有详尽地测试每种情况,但它能够可靠地正确编码和解码数字,并且输入和输出数字之间没有错误。

我编写的函数使用二进制字符串,但任何需要它的人都应该能够轻松地调整它以适应自己的用途。

这是我的代码:

--Define some commonly used constants here so we don't have to do this at runtime
--ln(2), used for change of base down the line
local log2 = math.log(2)

--Used to convert the fraction into a (very large) integer
local pow2to52 = math.pow(2,52)

--Used for bit-shifting
local f08 = math.pow(2, 8)
local f16 = math.pow(2,16)
local f24 = math.pow(2,24)
local f32 = math.pow(2,32)
local f40 = math.pow(2,40)
local f48 = math.pow(2,48)

function encodeDouble(number)
    --IEEE double-precision floating point number
    --Specification: https://en.wikipedia.org/wiki/Double-precision_floating-point_format

    --Separate out the sign, exponent and fraction
    local sign      = number < 0 and 1 or 0
    local exponent  = math.ceil(math.log(math.abs(number))/log2) - 1
    local fraction  = math.abs(number)/math.pow(2,exponent) - 1

    --Make sure the exponent stays in range - allowed values are -1023 through 1024
    if (exponent < -1023) then 
        --We allow this case for subnormal numbers and just clamp the exponent and re-calculate the fraction
        --without the offset of 1
        exponent = -1023
        fraction = math.abs(number)/math.pow(2,exponent)
    elseif (exponent > 1024) then
        --If the exponent ever goes above this value, something went horribly wrong and we should probably stop
        error("Exponent out of range: " .. exponent)
    end

    --Handle special cases
    if (number == 0) then
        --Zero
        exponent = -1023
        fraction = 0
    elseif (math.abs(number) == math.huge) then
        --Infinity
        exponent = 1024
        fraction = 0
    elseif (number ~= number) then
        --NaN
        exponent = 1024
        fraction = (pow2to52-1)/pow2to52
    end

    --Prepare the values for encoding
    local expOut = exponent + 1023                                  --The exponent is an 11 bit offset-binary
    local fractionOut = fraction * pow2to52                         --The fraction is 52 bit, so multiplying it by 2^52 will give us an integer


    --Combine the values into 8 bytes and return the result
    return char(
            128*sign + math.floor(expOut/16),                       --Byte 0: Sign and then shift exponent down by 4 bit
            (expOut%16)*16 + math.floor(fractionOut/f48),           --Byte 1: Shift fraction up by 4 to give most significant bits, and fraction down by 48
            math.floor(fractionOut/f40)%256,                        --Byte 2: Shift fraction down 40 bit
            math.floor(fractionOut/f32)%256,                        --Byte 3: Shift fraction down 32 bit
            math.floor(fractionOut/f24)%256,                        --Byte 4: Shift fraction down 24 bit
            math.floor(fractionOut/f16)%256,                        --Byte 5: Shift fraction down 16 bit
            math.floor(fractionOut/f08)%256,                        --Byte 6: Shift fraction down 8 bit
            math.floor(fractionOut % 256)                           --Byte 7: Last 8 bits of the fraction
        )
end

function decodeDouble(str)
    --Get bytes from the string
    local byte0 = byte(substr(str,1,1))
    local byte1 = byte(substr(str,2,2))
    local byte2 = byte(substr(str,3,3))
    local byte3 = byte(substr(str,4,4))
    local byte4 = byte(substr(str,5,5))
    local byte5 = byte(substr(str,6,6))
    local byte6 = byte(substr(str,7,7))
    local byte7 = byte(substr(str,8,8))

    --Separate out the values
    local sign = byte0 >= 128 and 1 or 0
    local exponent = (byte0%128)*16 + math.floor(byte1/16)
    local fraction = (byte1%16)*f48 
                     + byte2*f40 + byte3*f32 + byte4*f24 
                     + byte5*f16 + byte6*f08 + byte7

    --Handle special cases
    if (exponent == 2047) then
        --Infinities
        if (fraction == 0) then return math.pow(-1,sign) * math.huge end

        --NaN
        if (fraction == pow2to52-1) then return 0/0 end
    end

    --Combine the values and return the result
    if (exponent == 0) then
        --Handle subnormal numbers
        return math.pow(-1,sign) * math.pow(2,exponent-1023) * (fraction/pow2to52)
    else
        --Handle normal numbers
        return math.pow(-1,sign) * math.pow(2,exponent-1023) * (fraction/pow2to52 + 1)
    end
end

I actually needed to do this and came here for an answer. Doesn't look like anyone has done this before, so I ended up making something myself. I've not tested every case exhaustively, but it's able to reliably encode and decode numbers correctly, with no error between the input and output numbers.

The functions I wrote work with binary strings, but anyone who needs this should be able to easily adapt it for their own uses.

Here's my code:

--Define some commonly used constants here so we don't have to do this at runtime
--ln(2), used for change of base down the line
local log2 = math.log(2)

--Used to convert the fraction into a (very large) integer
local pow2to52 = math.pow(2,52)

--Used for bit-shifting
local f08 = math.pow(2, 8)
local f16 = math.pow(2,16)
local f24 = math.pow(2,24)
local f32 = math.pow(2,32)
local f40 = math.pow(2,40)
local f48 = math.pow(2,48)

function encodeDouble(number)
    --IEEE double-precision floating point number
    --Specification: https://en.wikipedia.org/wiki/Double-precision_floating-point_format

    --Separate out the sign, exponent and fraction
    local sign      = number < 0 and 1 or 0
    local exponent  = math.ceil(math.log(math.abs(number))/log2) - 1
    local fraction  = math.abs(number)/math.pow(2,exponent) - 1

    --Make sure the exponent stays in range - allowed values are -1023 through 1024
    if (exponent < -1023) then 
        --We allow this case for subnormal numbers and just clamp the exponent and re-calculate the fraction
        --without the offset of 1
        exponent = -1023
        fraction = math.abs(number)/math.pow(2,exponent)
    elseif (exponent > 1024) then
        --If the exponent ever goes above this value, something went horribly wrong and we should probably stop
        error("Exponent out of range: " .. exponent)
    end

    --Handle special cases
    if (number == 0) then
        --Zero
        exponent = -1023
        fraction = 0
    elseif (math.abs(number) == math.huge) then
        --Infinity
        exponent = 1024
        fraction = 0
    elseif (number ~= number) then
        --NaN
        exponent = 1024
        fraction = (pow2to52-1)/pow2to52
    end

    --Prepare the values for encoding
    local expOut = exponent + 1023                                  --The exponent is an 11 bit offset-binary
    local fractionOut = fraction * pow2to52                         --The fraction is 52 bit, so multiplying it by 2^52 will give us an integer


    --Combine the values into 8 bytes and return the result
    return char(
            128*sign + math.floor(expOut/16),                       --Byte 0: Sign and then shift exponent down by 4 bit
            (expOut%16)*16 + math.floor(fractionOut/f48),           --Byte 1: Shift fraction up by 4 to give most significant bits, and fraction down by 48
            math.floor(fractionOut/f40)%256,                        --Byte 2: Shift fraction down 40 bit
            math.floor(fractionOut/f32)%256,                        --Byte 3: Shift fraction down 32 bit
            math.floor(fractionOut/f24)%256,                        --Byte 4: Shift fraction down 24 bit
            math.floor(fractionOut/f16)%256,                        --Byte 5: Shift fraction down 16 bit
            math.floor(fractionOut/f08)%256,                        --Byte 6: Shift fraction down 8 bit
            math.floor(fractionOut % 256)                           --Byte 7: Last 8 bits of the fraction
        )
end

function decodeDouble(str)
    --Get bytes from the string
    local byte0 = byte(substr(str,1,1))
    local byte1 = byte(substr(str,2,2))
    local byte2 = byte(substr(str,3,3))
    local byte3 = byte(substr(str,4,4))
    local byte4 = byte(substr(str,5,5))
    local byte5 = byte(substr(str,6,6))
    local byte6 = byte(substr(str,7,7))
    local byte7 = byte(substr(str,8,8))

    --Separate out the values
    local sign = byte0 >= 128 and 1 or 0
    local exponent = (byte0%128)*16 + math.floor(byte1/16)
    local fraction = (byte1%16)*f48 
                     + byte2*f40 + byte3*f32 + byte4*f24 
                     + byte5*f16 + byte6*f08 + byte7

    --Handle special cases
    if (exponent == 2047) then
        --Infinities
        if (fraction == 0) then return math.pow(-1,sign) * math.huge end

        --NaN
        if (fraction == pow2to52-1) then return 0/0 end
    end

    --Combine the values and return the result
    if (exponent == 0) then
        --Handle subnormal numbers
        return math.pow(-1,sign) * math.pow(2,exponent-1023) * (fraction/pow2to52)
    else
        --Handle normal numbers
        return math.pow(-1,sign) * math.pow(2,exponent-1023) * (fraction/pow2to52 + 1)
    end
end
困倦 2025-01-09 15:23:11

8 年后,幸运的是,情况发生了变化:

这里有一个仍在更新的纯 lua 库,它与其他结构库非常相似:https://luarocks.org/modules/deepakjois/vstruct

在其他格式中,它可以解析任何字节顺序的浮点数和双精度数。

例子:

local readfloat = vstruct.compile("f4") -- compile a parser, f4 is a 4 byte float
local results = {}
readfloat:read("aaaa",results) -- can return either a new table or reuse one as done here
print(results[1]) -- 2.5984589414244e+20

After 8 years the landscape has fortunately changed:

A still being updated pure lua library that closely resembles other struct libraries exists here: https://luarocks.org/modules/deepakjois/vstruct

Among other formats it can parse both floats and doubles in any endianess.

Example:

local readfloat = vstruct.compile("f4") -- compile a parser, f4 is a 4 byte float
local results = {}
readfloat:read("aaaa",results) -- can return either a new table or reuse one as done here
print(results[1]) -- 2.5984589414244e+20
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文