全站通知:

模块:Utils/Float32

来自星露谷物语维基
跳到导航 跳到搜索

鸣谢

本模块模拟了 C# 环境下的 Float32 计算,用于修正浮点数计算导致的误差。相关代码由玉米芯完成。

[ 查看 | 编辑 | 历史 | 刷新 ]上述文档的内容来自模块:Utils/Float32/doc
-- Float32Utils 模块
-- 提供 IEEE 754 单精度浮点数处理功能
-- 
-- IEEE 754 单精度浮点数格式:
-- - 1位符号位 (S)
-- - 8位指数位 (E),偏移量为127
-- - 23位尾数位 (M)
-- 
-- 总共32位,按大端序排列:SEEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM

local p = {}

-- 将数字打包为 Float32 格式的4字节字符串
-- @param number 要转换的数字
-- @return 4字节的字符串,表示IEEE 754单精度浮点数
function p.PackFloat32(number)
    -- 处理零值:返回全零字节
    if number == 0 then
        return string.char(0x00, 0x00, 0x00, 0x00)
    -- 处理NaN:返回特殊的NaN表示
    elseif number ~= number then
        return string.char(0xFF, 0xFF, 0xFF, 0xFF)
    else
        -- 提取符号位
        local sign = 0x00
        if number < 0 then
            sign = 0x80  -- 设置符号位为1
            number = -number  -- 转为正数处理
        end
        
        -- 使用math.frexp分解数字为尾数和指数
        -- frexp返回 (mantissa, exponent),其中 number = mantissa * 2^exponent
        -- mantissa在[0.5, 1)范围内
        local mantissa, exponent = math.frexp(number)
        
        -- 调整指数:加上IEEE 754的偏移量127
        exponent = exponent + 0x7F
        
        -- 处理非规格化数(指数<=0)
        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
                -- 规格化数:调整尾数去掉隐含的1
                mantissa = mantissa * 2 - 1
                exponent = exponent - 1
            end
        end
        
        -- 将尾数转换为23位整数(四舍五入)
        mantissa = math.floor(math.ldexp(mantissa, 23) + 0.5)
        
        -- 组装4个字节
        -- 字节1:符号位 + 指数高7位
        -- 字节2:指数低1位 + 尾数高7位
        -- 字节3:尾数中间8位
        -- 字节4:尾数低8位
        return string.char(
                sign + math.floor(exponent / 2),
                (exponent % 2) * 0x80 + math.floor(mantissa / 0x10000),
                math.floor(mantissa / 0x100) % 0x100,
                mantissa % 0x100)
    end
end

-- 从 Float32 格式的4字节字符串解包为数字
-- @param packed 4字节的字符串,表示IEEE 754单精度浮点数
-- @return 解包后的数字值
function p.UnpackFloat32(packed)
    -- 提取4个字节
    local b1, b2, b3, b4 = string.byte(packed, 1, 4)
    
    -- 重构指数:从字节1的低7位和字节2的高1位
    local exponent = (b1 % 0x80) * 0x02 + math.floor(b2 / 0x80)
    
    -- 重构尾数:从字节2的低7位、字节3和字节4
    -- 并转换为[0,1)范围的小数
    local mantissa = math.ldexp(((b2 % 0x80) * 0x100 + b3) * 0x100 + b4, -23)
    
    -- 处理特殊指数值
    if exponent == 0xFF then
        -- 指数全1:无穷大或NaN
        if mantissa > 0 then
            return 0 / 0  -- NaN
        else
            mantissa = math.huge  -- 无穷大
            exponent = 0x7F
        end
    elseif exponent > 0 then
        -- 规格化数:添加隐含的1
        mantissa = mantissa + 1
    else
        -- 非规格化数:调整指数
        exponent = exponent + 1
    end
    
    -- 处理符号位
    if b1 >= 0x80 then
        mantissa = -mantissa
    end
    
    -- 使用ldexp重构最终数值:mantissa * 2^(exponent - 127)
    return math.ldexp(mantissa, exponent - 0x7F)
end

-- 将数字转换为 Float32 精度
-- 通过打包再解包的过程,模拟单精度浮点数的精度损失
-- @param number 输入的数字
-- @return 转换为Float32精度后的数字
function p.toFloat32(number)
    return p.UnpackFloat32(p.PackFloat32(number))
end

-- Float32 精度的加法运算
-- 确保加法结果符合单精度浮点数的精度限制
-- @param a 第一个加数
-- @param b 第二个加数
-- @return Float32精度的加法结果
function p.AddFloat32(a, b)
    local a_f = p.toFloat32(a)  -- 转换为Float32精度
    local b_f = p.toFloat32(b)  -- 转换为Float32精度
    return p.toFloat32(a_f + b_f)  -- 加法后再次转换确保精度
end

-- Float32 精度的乘法运算
-- 确保乘法结果符合单精度浮点数的精度限制
-- @param a 被乘数
-- @param b 乘数
-- @return Float32精度的乘法结果
function p.MulFloat32(a, b)
    local a_f = p.toFloat32(a)  -- 转换为Float32精度
    local b_f = p.toFloat32(b)  -- 转换为Float32精度
    return p.toFloat32(a_f * b_f)  -- 乘法后再次转换确保精度
end

-- Float32 乘法运算(从 MediaWiki frame 参数获取输入)
-- 用于 MediaWiki 模板调用,从 frame.args 中获取参数 a 和 b
-- @param frame MediaWiki frame 对象,包含 args.a 和 args.b 参数
-- @return Float32精度的乘法结果,如果参数无效则返回0
function p.MulFloat32Args(frame)
    local a = tonumber(frame.args.a)  -- 从frame获取参数a并转换为数字
    local b = tonumber(frame.args.b)  -- 从frame获取参数b并转换为数字

    -- 参数验证:如果任一参数为nil,返回0
    if a == nil or b == nil then
        return 0
    end

    return p.MulFloat32(a, b)
end

-- Float32 数字的向下取整(截断)
-- 先转换为Float32精度,再进行向下取整
-- @param number 要截断的数字
-- @return 截断后的整数
function p.TruncFloat32(number)
    return math.floor(p.toFloat32(number))
end

return p