Lua - packing IEEE754 single-precision floating-point numbers
Asked Answered
D

2

11

I want to make a function in pure Lua that generates a fraction (23 bits), an exponent (8 bits), and a sign (1 bit) from a number, so that the number is approximately equal to math.ldexp(fraction, exponent - 127) * (sign == 1 and -1 or 1), and then packs the generated values into 32 bits.

A certain function in the math library caught my attention:

The frexp function breaks down the floating-point value (v) into a mantissa (m) and an exponent (n), such that the absolute value of m is greater than or equal to 0.5 and less than 1.0, and v = m * 2^n.

Note that math.ldexp is the inverse operation.

However, I can't think of any way to pack non-integer numbers properly. As the the mantissa returned by this function is not an integer, I'm not sure if I can use it.

Is there any efficient way to do something similar to math.frexp() which returns an integer as the mantissa? Or is there perhaps a better way to pack numbers in the IEEE754 single-precision floating-point format in Lua?

Thank you in advance.

Edit

I hereby present the (hopefully) final version of the functions I made:

function PackIEEE754(number)
    if number == 0 then
        return string.char(0x00, 0x00, 0x00, 0x00)
    elseif number ~= number then
        return string.char(0xFF, 0xFF, 0xFF, 0xFF)
    else
        local sign = 0x00
        if number < 0 then
            sign = 0x80
            number = -number
        end
        local mantissa, exponent = math.frexp(number)
        exponent = exponent + 0x7F
        if exponent <= 0 then
            mantissa = math.ldexp(mantissa, exponent - 1)
            exponent = 0
        elseif exponent > 0 then
            if exponent >= 0xFF then
                return string.char(sign + 0x7F, 0x80, 0x00, 0x00)
            elseif exponent == 1 then
                exponent = 0
            else
                mantissa = mantissa * 2 - 1
                exponent = exponent - 1
            end
        end
        mantissa = math.floor(math.ldexp(mantissa, 23) + 0.5)
        return string.char(
                sign + math.floor(exponent / 2),
                (exponent % 2) * 0x80 + math.floor(mantissa / 0x10000),
                math.floor(mantissa / 0x100) % 0x100,
                mantissa % 0x100)
    end
end
function UnpackIEEE754(packed)
    local b1, b2, b3, b4 = string.byte(packed, 1, 4)
    local exponent = (b1 % 0x80) * 0x02 + math.floor(b2 / 0x80)
    local mantissa = math.ldexp(((b2 % 0x80) * 0x100 + b3) * 0x100 + b4, -23)
    if exponent == 0xFF then
        if mantissa > 0 then
            return 0 / 0
        else
            mantissa = math.huge
            exponent = 0x7F
        end
    elseif exponent > 0 then
        mantissa = mantissa + 1
    else
        exponent = exponent + 1
    end
    if b1 >= 0x80 then
        mantissa = -mantissa
    end
    return math.ldexp(mantissa, exponent - 0x7F)
end

I improved the way to utilise the implicit bit and added proper support for special values such as NaN and infinity. I based the formatting on that of the script catwell linked to.

I thank both of you for your great advice.

Dariodariole answered 19/1, 2013 at 17:11 Comment(0)
S
7

Multiply the significand you get from math.frexp() by 2^24, and subtract 24 from the exponent to compensate. Now the significand is an integer. Note that the significand is 24 bits, not 23 (you need to account for the implicit bit in the IEEE-754 encoding).

Syst answered 19/1, 2013 at 17:14 Comment(0)
A
3

Have you seen this?

I think it does what you want in a slighty simpler way.

Aube answered 21/1, 2013 at 9:48 Comment(1)
Thank you, I just checked it. The method it uses is very similar to that of mine, but uses cleaner formatting. However, there are a few problems with it (it doesn't seem to support zero or very small numbers), so I'm making my own version based on it. I'll update my post when it's done.Dariodariole

© 2022 - 2024 — McMap. All rights reserved.