浮點數在內存中的存儲可以參考《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