浮点数在内存中的存储可以参考《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 | 全是0 | 全是0 | 固定值0 |
--将整数转为浮点数float
local function Hex2Float(iValue)--[[--以下计算方法参考: https://docs.oracle.com/javase/6/docs/api/java/lang/Float.html#floatToIntBits(float)iValue = iValue&0xFFFFFFFFif 0x7f800000==iValue then --正无穷大return math.hugeelseif 0xff800000==iValue then --负无穷大return -math.hugeelseif (0x7f800001<=iValue and iValue<=0x7fffffff) or (0xff800001<=iValue and iValue<=0xffffffff) then --不是一个有效的数字return nilend--是一个有效的数字local bits = iValuelocal sif (bits>>31)&0x1==0 then s = 1else s = -1endlocal e = (bits>>23) & 0xfflocal mif 0==e then m = (bits & 0x7fffff) << 1else m = (bits & 0x7fffff) | 0x800000endreturn s*m*(2^(e-150)) -- s * m/(2^23) * 2^(e-127) 中间一部分相当于是十进制里面的 123/100=1.23一个操作]]----[[--详细解释实现原理--s e m分别占位: 1 8 23iValue = iValue&0xFFFFFFFFlocal s = (iValue>>31)&0x01local e = (iValue>>23)&0xFFlocal m = iValue&0x7FFFFFlocal S = slocal E = e-127local M = 1if e==0 and m==0 then --指数和尾数都是0的,表示0return 0.0 elseif e==0xFF and m==0 then --指数全是1,而尾数都是0的,表示无穷if s==1 thenreturn -math.hugeelsereturn math.hugeendelseif e==0xFF and m~=0 then --指数全是1,而尾数不全是0的,表示NaN(不是一个数)return nilelseif e==0 and m~=0 then --指数全是0,而尾数不全是0的,表示一种非规格化的数字E=-126 -- 1-127M = 0endfor i=1,23 doif (m>>(23-i))&0x01==0x01 thenM = M+2^(-i)endend-- f = (-1)^s * m * 2^ereturn ((-1)^S) * M * (2^E)]]--local binary = {}for i=3,0,-1 dotable.insert(binary,(iValue>>(i*8))&0xFF)endlocal 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&0xFFFFFFFFFFFFFFFFif 0x7ff0000000000000==iValue then --正无穷大return math.hugeelseif 0xfff0000000000000==iValue then --负无穷大return -math.hugeelseif (0x7ff0000000000001<=iValue and iValue<=0x7fffffffffffffff) or (0xfff0000000000001<=iValue and iValue<=0xffffffffffffffff) then --不是一个有效的数字return nilend--是一个有效的数字local bits = iValuelocal sif (bits>>63)&0x1==0 then s = 1else s = -1endlocal e = (bits>>52) & 0x7fflocal mif 0==e then m = (bits & 0xfffffffffffff) << 1else m = (bits & 0xfffffffffffff) | 0x10000000000000endreturn s*m*(2^(e-1075))]]----[[--详细解释实现原理--s e m分别占位: 1 11 52iValue = iValue&0xFFFFFFFFFFFFFFFFlocal s = (iValue>>63)&0x01local e = (iValue>>52)&0x7FFlocal m = iValue&0xFFFFFFFFFFFFFlocal S = slocal E = e-1023local M = 1if e==0 and m==0 then --指数和尾数都是0的,表示0return 0.0 elseif e==0x7FF and m==0 then --指数全是1,而尾数都是0的,表示无穷if s==1 thenreturn -math.hugeelsereturn math.hugeendelseif e==0x7FF and m~=0 then --指数全是1,而尾数不全是0的,表示NaN(不是一个数)return nilelseif e==0 and m~=0 then --一种非规格化的数字E=-1022 -- 1-1023M = 0endfor i=1,52 doif (m>>(52-i))&0x01==0x01 thenM = M+2^(-i)endend-- f = (-1)^s * m * 2^ereturn ((-1)^S) * M * (2^E)]]--local binary = {}for i=7,0,-1 dotable.insert(binary,(iValue>>(i*8))&0xFF)endlocal v,pos = string.unpack(">d", string.char(table.unpack(binary)))return v
end