浮点数在内存中的存储结构

浮点数在内存中的存储可以参考《IEEE754标准》https://people.eecs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF

参考博文:IEEE754详解(最详细简单有趣味的介绍)-CSDN博客



单精度float占内存4字节,最高位bit31表示符号位,指数位bit23~bit30占用8个bit位,剩余的23个bit表示尾数。

双精度double占用内存8字节,最高位bit63表示符号位,指数位bit52~bit62占用11个bit位,剩余的52个bit表示尾数。

字节长度符号位S指数位E尾数M
单精度4字节1 [31]8 [30-23]23 [22-0]
双精度8字节1 [63]11 [62-52]52 [51-0]



任何一个数,都可以使用不同的权重表示,最终的计算结果公式如下:

假如有一个数:AnAn-1An-2...A2A1B1B2...Bn-1Bn,其中A1~An表示整数部分的每一位数,B1~Bn表示小数部分的每一位数。权重为Q,则

S = An*Q^(n-1)+An-1*Q^(n-2)+...+A2*Q^1+A1*Q^0+B1*Q^(-1)+B2*Q^(-2)+...Bn-1*Q^(-(n-1))+Bn*Q^(-n)

例如十进制数:123.456,对应的十进制数是

S = 1*10^2+2*10^1+3*10^0+4*10^(-1)+5*10^(-2)+6*10^(-3)

再比如二进制数:1011.011,对应的十进制数是

S = 1*2^3+0*2^2+1*2^1+1*2^0+0*2^(-1)+1*2^(-2)+1*2^(-3) = 11.365

再比如八进制数:56.76,对应的十进制是

S = 5*8^1+6*8^0+7*8^(-1)+6*8^(-2) = 46.96875

将10进制数转为其他进制,比如将整数为A,小数为B变为b进制(123.456:整数A=123,小数B=0.456),则步骤如下:

整数部分:

1. A除以b得到商B和余数R1

2. B除以b得到商C和余数R2

3. .................................Rn-1

4. 重复2,直到商为0,余数为Rn

则整数部分是:RnRn-1...R2R1

小数部分:

1. 小数B乘以b得到积B',再将B'拆分成整数部分F1和小数C

2. 小数C乘以b得到积C',再将C'拆分成整数部分F2和小数D

3. 小数D乘以b得到积D',再将D'拆分成整数部分F3和小数E

4. 重复上述过程,直到小数为0。(有可能永远不会等于0)

则小数部分是:F1F2F3...Fn-1Fn..........

所以最终10进制转为其他进制的结果就是:RnRn-1...R2R1.F1F2F3...Fn-1Fn.........

注意R1和F1之间有一个小数点。



有了上面的基础认知后,我们来看看浮点数是如何存储在内存中的

先说单精度

指数是8位bit,所以可以表示为0~255,但是指数存在正数和负数,所以IEEE754规定,存入内存时E的真实值必须再加上一个中间数127(2^7-1)

再说双精度

指数是11位bit,所以可以表示为0~2047,但是指数存在正数和负数,所以IEEE754规定,存入内存时E的真实值必须再加上一个中间数1023(2^11-1)

例:2^10的E是10,所以保存为32位浮点数的时候,E必须保存为10+127=137,即10001001

保存为64位浮点数的时候,E保存为10+1023=1033,即10000001001



根据《IEEE754标准》规定,浮点数在计算机中是以科学计数表示方法存储的,例如123.456表示为1.23456*10^2

下面使用例子来说明存储浮点数(正数和负数计算方式一样,唯一区别就是最高bit位不同而已)

十进制数:123.456

按照公式,它的二进制是:1111011.0111010010111100011010100111111011111001110110.....后面省略不计算

它的科学表示方式是1.1110110111010010111100011010100111111011111001110110.....*2^6所以如下(以单精度为例):

符号位:S=0

指数位:E=6+127=133,二进制是 10000101

尾数:M=1.1110110111010010111100011010100111111011111001110110......按照科学计数表示法来规定,小数点前面一定是1,所以IEEE754为了节省1一个bit,默认将小数点前面的1省略掉,所以实际M=1110110111010010111100011010100111111011111001110110......,右因为单精度的M只占23位,所以M=11101101110100101111001(第24位是1,需要进一位,遵循逢二进一原则)

所以最终的123.456在内存中的二进制是:

0 10000101 11101101110100101111001=0x42F6E979

十进制数:0.0456

二进制为:0.000010111010110001110001000011001011001010010101111010011110000...

科学表示:1.0111010110001110001000011001011001010010101111010011110000...*2^(-5)所以如下(单精度):

符号位:S=0

指数位:E=-5+127=122,二进制是 01111010

尾数:M=01110101100011100010001

所以最终的0.0456在内存中的二进制是:

0 01111010 01110101100011100010001=0x3D3AC711



十进制浮点数转为二进制存储的计算步骤:

1. 确定符号位,正数S=0,负数S=1

2. 将正数转为二进制表示,例如123.456=1111011.0111010010111100011010100111111011111001110110.....

0.0456=0.000010111010110001110001000011001011001010010101111010011110000...

3. 将小数点进行左移/右移e位,使得小数点前面有且仅有一个有意义的数字1,即:变为科学计数表达形式。(左移得到的指数是正数,右移得到的是负数)

123.456的二进制小数点需要左移6位

0.0456的二进制小数点需要右移5位

4. 指数加上中间数base(单精度base=127,双精度base=1023)

    E = e+base

   123.456的E=6+127=133                 0.0456的E=-5+127=122

5. 将科学计数法表示的二进制数,从小数点后面第一位开始(因为小数点前面肯定是1,为了节省1bit,所以去掉),从左到右依次填充到尾数位最后一位遵循逢二进一原则,从而得到M

6. 将S  E   M分别填充到对应的位,即可得到二进制存储。



内存中关于二进制转为浮点数

根据国际标准 IEEE754,任意一个浮点数都可以用如下形式来表示:

V = (-1)^S * M * 2^E。
符号 S 决定浮点数的是负数(S=1)还是正数(S=0),由一位符号位表示。
有效数M是一个二进制小数,它的范围在1~2之间。
指数E是2的幂,可正可负,作用是对浮点数加权,由8位或11位的指数域表示。

浮点数的二进制解析有以下解码格式
指数域E尾数域M说明
格式化值------

符合一般公式

此时的 E = E-base

非格式化值全是1全是0表示无穷
全是1不全是0表示NaN(不是一个有效数字)
特殊值全是0不全是0

此时的E=1-base。

M不再加上第一位的1,而是还原为0.xxxxxx的小数

0全是0全是0固定值0
--将整数转为浮点数float
local function Hex2Float(iValue)
    --[[
    --以下计算方法参考: https://docs.oracle.com/javase/6/docs/api/java/lang/Float.html#floatToIntBits(float)
    iValue = iValue&0xFFFFFFFF
    if 0x7f800000==iValue then --正无穷大
        return math.huge
    elseif 0xff800000==iValue then --负无穷大
        return -math.huge
    elseif (0x7f800001<=iValue and iValue<=0x7fffffff) or (0xff800001<=iValue and iValue<=0xffffffff) then --不是一个有效的数字
        return nil
    end
    
    --是一个有效的数字
    local bits = iValue
    local s
    if (bits>>31)&0x1==0 then s = 1
    else s = -1
    end

    local e = (bits>>23) & 0xff

    local m
    if 0==e then m = (bits & 0x7fffff) << 1
    else m = (bits & 0x7fffff) | 0x800000
    end

    return s*m*(2^(e-150)) -- s * m/(2^23) * 2^(e-127) 中间一部分相当于是十进制里面的 123/100=1.23一个操作
    ]]--
    
    --[[
    --详细解释实现原理
    --s e m分别占位: 1 8 23
    iValue = iValue&0xFFFFFFFF
    local s = (iValue>>31)&0x01
    local e = (iValue>>23)&0xFF
    local m = iValue&0x7FFFFF
    
    local S = s
    local E = e-127
    local M = 1
    if e==0 and m==0 then --指数和尾数都是0的,表示0
        return 0.0 
    elseif e==0xFF and m==0 then --指数全是1,而尾数都是0的,表示无穷
        if s==1 then
            return -math.huge
        else
            return math.huge
        end
    elseif e==0xFF and m~=0 then --指数全是1,而尾数不全是0的,表示NaN(不是一个数)
        return nil
    elseif e==0 and m~=0 then --指数全是0,而尾数不全是0的,表示一种非规格化的数字
        E=-126 -- 1-127
        M = 0
    end
    
    for i=1,23 do
        if (m>>(23-i))&0x01==0x01 then
            M = M+2^(-i)
        end
    end

    -- f = (-1)^s * m * 2^e
    return ((-1)^S) * M * (2^E)
    ]]--
    local binary = {}
    for i=3,0,-1 do
        table.insert(binary,(iValue>>(i*8))&0xFF)
    end
    local v,pos = string.unpack(">f", string.char(table.unpack(binary)))
    return v
end

--将整数转为浮点数double
local function Hex2Double(iValue)
    --[[
    --以下计算方法参考: https://docs.oracle.com/javase/6/docs/api/java/lang/Double.html#doubleToLongBits(double)
    iValue = iValue&0xFFFFFFFFFFFFFFFF
    if 0x7ff0000000000000==iValue then --正无穷大
        return math.huge
    elseif 0xfff0000000000000==iValue then --负无穷大
        return -math.huge
    elseif (0x7ff0000000000001<=iValue and iValue<=0x7fffffffffffffff) or (0xfff0000000000001<=iValue and iValue<=0xffffffffffffffff) then --不是一个有效的数字
        return nil
    end
    
    --是一个有效的数字
    local bits = iValue
    local s
    if (bits>>63)&0x1==0 then s = 1
    else s = -1
    end

    local e = (bits>>52) & 0x7ff

    local m
    if 0==e then m = (bits & 0xfffffffffffff) << 1
    else m = (bits & 0xfffffffffffff) | 0x10000000000000
    end

    return s*m*(2^(e-1075))
    ]]--
    
    --[[
    --详细解释实现原理
    --s e m分别占位: 1 11 52
    iValue = iValue&0xFFFFFFFFFFFFFFFF
    local s = (iValue>>63)&0x01
    local e = (iValue>>52)&0x7FF
    local m = iValue&0xFFFFFFFFFFFFF
    
    local S = s
    local E = e-1023
    local M = 1
    if e==0 and m==0 then --指数和尾数都是0的,表示0
        return 0.0 
    elseif e==0x7FF and m==0 then --指数全是1,而尾数都是0的,表示无穷
        if s==1 then
            return -math.huge
        else
            return math.huge
        end
    elseif e==0x7FF and m~=0 then --指数全是1,而尾数不全是0的,表示NaN(不是一个数)
        return nil
    elseif e==0 and m~=0 then --一种非规格化的数字
        E=-1022 -- 1-1023
        M = 0
    end
    
    for i=1,52 do
        if (m>>(52-i))&0x01==0x01 then
            M = M+2^(-i)
        end
    end

    -- f = (-1)^s * m * 2^e
    return ((-1)^S) * M * (2^E)
    ]]--
    local binary = {}
    for i=7,0,-1 do
        table.insert(binary,(iValue>>(i*8))&0xFF)
    end
    local v,pos = string.unpack(">d", string.char(table.unpack(binary)))
    return v
end

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值