1.數(shù)據(jù)存儲(chǔ)
float的指數(shù)位有8位,而double的指數(shù)位有11位,分布如下:
float:
1bit(符號(hào)位) | 8bits(指數(shù)位) | 23bits(尾數(shù)位) |
double:
1bit(符號(hào)位) | 11bits(指數(shù)位) | 52bits(尾數(shù)位) |
在數(shù)學(xué)中,特別是在計(jì)算機(jī)相關(guān)的數(shù)字(浮點(diǎn)數(shù))問(wèn)題的表述中,有一個(gè)基本表達(dá)法:
valueoffloating-point= significandx base^exponent,withsign
即:數(shù)值 = 尾數(shù) ×底數(shù)^指數(shù),(附加正負(fù)號(hào))
其實(shí)不論是float還是double在存儲(chǔ)方式上都是遵從IEEE的規(guī)范的,float遵從的是IEEER32.24,而double遵從的是R64.53。R32. 24和R64.53的存儲(chǔ)方式都是用科學(xué)計(jì)數(shù)法來(lái)存儲(chǔ)數(shù)據(jù)的。
指數(shù)部分的存儲(chǔ)采用移位存儲(chǔ),存儲(chǔ)的數(shù)據(jù)為元數(shù)據(jù)+127,下面就看看8.25和120.5在內(nèi)存中真正的存儲(chǔ)方式。
8.25用十進(jìn)制的科學(xué)計(jì)數(shù)法表示就為:8.25*10^0,而120.5可以表示為:1.205*10^2。而在計(jì)算機(jī)存儲(chǔ)中,首先要將上面的數(shù)更改為二進(jìn)制的科學(xué)計(jì)數(shù)法表示,8.25用二進(jìn)制表示可表示為1000.01,120.5用二進(jìn)制表示為:1110110.1用二進(jìn)制的科學(xué)計(jì)數(shù)法表示1000.01可以表示為1.0001*2^3,1110110.1可以表示為1.1101101*2^6。
首先看下8.25,二進(jìn)制:1.0001*2^3
按照上面的存儲(chǔ)方式,符號(hào)位為:0,表示為正,指數(shù)位為:3+127=130,位數(shù)部分為,故8.25的存儲(chǔ)方式如下圖所示:
而單精度浮點(diǎn)數(shù)120.5的存儲(chǔ)方式如下圖所示:
對(duì)于一個(gè)小數(shù)其默認(rèn)類型為double型,如果需要指定為float型,需要在小數(shù)后面添加F或f后綴。
如floata=0.5f;
2.范圍
范圍主要是來(lái)討論最大最小值。
float和double的范圍是由指數(shù)的位數(shù)來(lái)決定的。對(duì)于指數(shù)部分,因?yàn)橹笖?shù)可正可負(fù),8位的指數(shù)位能表示的指數(shù)范圍就應(yīng)該為:-127~+128了。
即float的指數(shù)范圍為-127 ~+128(+-2^7),
而double的指數(shù)范圍為-1023 ~ +1024 (+-2^10),
并且指數(shù)位是按補(bǔ)碼的形式來(lái)劃分的。
其中負(fù)指數(shù)決定了浮點(diǎn)數(shù)所能表達(dá)的絕對(duì)值最小的非零數(shù);而正指數(shù)決定了浮點(diǎn)數(shù)所能表達(dá)的絕對(duì)值最大的數(shù),也即決定了浮點(diǎn)數(shù)的取值范圍。
float的范圍為-2^128 ~+2^128 也即-3.40E+38~+3.40E+38;
double的范圍為-2^1024~+2^1024,也即-1.79E+308~+1.79E+308。
類型名 | 占內(nèi)存字節(jié)數(shù)(B) | 值的范圍 |
float | 4 | -3.40E+38~+3.40E+38 |
double | 8 | -1.79E+308~+1.79E+308 |
longdouble | 10 | -3.4E+4932~+1.1E+4932 |
其他特殊表示
111111111尾數(shù)的23位不全為0:表示不是數(shù)值
11111111100000000000000000000000:表示負(fù)無(wú)窮大.比最小負(fù)數(shù)還小的數(shù)都視為負(fù)無(wú)窮大.
11111111011111111111111111111111:表示最小的負(fù)數(shù),即-2^128=-3.4*10^38
10000000100000000000000000000000:表示最大的負(fù)數(shù),即-2^(-126)=-1.18*10^(-38)
00000000000000000000000000000000:表示0
00000000100000000000000000000000:表示最小的正數(shù),即2^(-126)=1.18*10^(-38)
01111111011111111111111111111111:表示最大的正數(shù),即2^(128)=3.4*10^(38)
01111111100000000000000000000000:表示正無(wú)窮大,比最大正數(shù)還大的數(shù)都視為正無(wú)窮大.
011111111尾數(shù)的23位不全為0:表示不是數(shù)值
當(dāng)指數(shù)部分和小數(shù)部分全為0時(shí),表示0值,有+0和-0之分(符號(hào)位決定),0x00000000表示正0,0x80000000表示負(fù)0.
指數(shù)部分全1,小數(shù)部分全0時(shí),表示無(wú)窮大,有正無(wú)窮和負(fù)無(wú)窮,0x7f800000表示正無(wú)窮,0xff800000表示負(fù)無(wú)窮.
故,float型數(shù)據(jù)的表示范圍包括:
負(fù)無(wú)窮大;-3.4*10^38<x<-1.175*10^(-38);0;1.175*10^(-38)<x<3.4*10^38;正無(wú)窮大.
3.精度
精度主要是來(lái)討論數(shù)據(jù)通過(guò)科學(xué)計(jì)數(shù)法表達(dá)后,小數(shù)點(diǎn)后數(shù)據(jù)位數(shù)。
float和double的精度是由尾數(shù)的位數(shù)來(lái)決定的。任何一個(gè)數(shù)的科學(xué)計(jì)數(shù)法表示都為1.xxx*2^n,尾數(shù)部分就可以表示為xxxx,第一位都是1,可以將小數(shù)點(diǎn)前面的1省略,故不能對(duì)精度造成影響。
那么對(duì)于尾數(shù)
float:2^23=8388608,一共七位,這意味著最多能有7位有效數(shù)字,但絕對(duì)能保證的為6位,也即float的精度為6~7位有效數(shù)字;
double:2^52=4503599627370496,一共16位,同理,double的精度為15~16位。
或,
log(2^23)=6.xx,取整即6;
log(2^53)=15.95,取整即15;
log(2^65)=19.57,取整即19。
4.問(wèn)題
inti=Int32.MaxValue;
floatf=i;
intj=(int)f;
boolb=i==j;
這里的b,是false。剛才這個(gè)操作,如果我們把float換成long,第一次進(jìn)行隱式轉(zhuǎn)換,第二次進(jìn)行強(qiáng)制轉(zhuǎn)換,結(jié)果將會(huì)是true。乍一看,float.MaxValue是比int.MaxValue大了不知道多少倍的,然而這個(gè)隱式轉(zhuǎn)換中,卻造成了數(shù)據(jù)丟失。int.MaxValue,這個(gè)值等于2^31-1,寫成二進(jìn)制補(bǔ)碼形式就是01111…(31個(gè)1),這個(gè)數(shù),在表示成float計(jì)數(shù)的科學(xué)計(jì)數(shù)法的時(shí)候,將會(huì)寫成+0.1111…(23個(gè)1)*2^31,對(duì)于那31個(gè)1,里面的最后8個(gè),被float拋棄了,因此,再將這個(gè)float強(qiáng)制轉(zhuǎn)換回int的時(shí)候,對(duì)應(yīng)的int的二進(jìn)制補(bǔ)碼表示已經(jīng)變成了0111…(23個(gè)1)00000000,這個(gè)數(shù)與最初的那個(gè)int相差了255,所以造成了不相等。
那么提出另一個(gè)問(wèn)題,什么樣的int變成float再變回來(lái),和從前的值相等呢?這個(gè)問(wèn)題其實(shí)完全出在那23位float的數(shù)據(jù)位上了。對(duì)于一個(gè)int,把它寫成二進(jìn)制形式之后,成為了個(gè)一32個(gè)長(zhǎng)度的0、1的排列,對(duì)于這個(gè)排列,只要第一個(gè)1與最后一個(gè)1之前的間距,不超過(guò)23,那么它轉(zhuǎn)換成float再轉(zhuǎn)換回來(lái),兩個(gè)值就會(huì)相等。這個(gè)問(wèn)題是與大小無(wú)關(guān)的,而且這個(gè)集合在int這個(gè)全集下并不連續(xù)。
floatf=2.2f;
doubled=(double)f;
Console.WriteLine(d.ToString("0.0000000000000"));
f=2.25f;
d=(double)f;
Console.WriteLine(d.ToString("0.0000000000000"));
可能輸出的結(jié)果讓大家疑惑不解,單精度的2.2轉(zhuǎn)換為雙精度后,精確到小數(shù)點(diǎn)后13位后變?yōu)榱?.2000000476837,而單精度的2.25轉(zhuǎn)換為雙精度后,變?yōu)榱?.2500000000000,為何2.2在轉(zhuǎn)換后的數(shù)值更改了而2.25卻沒有更改呢?首先我們看看2.25的單精度存儲(chǔ)方式,01000000100100000000000000000000,而2.25的雙精度表示為:0100000000010010000000000000000000000000000000000000000000000000,這樣2.25在進(jìn)行強(qiáng)制轉(zhuǎn)換的時(shí)候,數(shù)值是不會(huì)變的,而我們?cè)倏纯?.2呢,2.2用科學(xué)計(jì)數(shù)法表示應(yīng)該為:將十進(jìn)制的小數(shù)轉(zhuǎn)換為二進(jìn)制的小數(shù)的方法為將小數(shù)*2,取整數(shù)部分,所以0.282=0.4,所以二進(jìn)制小數(shù)第一位為0.4的整數(shù)部分0,0.4×2=0.8,第二位為0,0.8*2=1.6,第三位為1,0.6×2=1.2,第四位為1,0.2*2=0.4,第五位為0,這樣永遠(yuǎn)也不可能乘到=1.0,得到的二進(jìn)制是一個(gè)無(wú)限循環(huán)的排列00110011001100110011...,對(duì)于單精度數(shù)據(jù)來(lái)說(shuō),尾數(shù)只能表示24bit的精度,所以2.2的float存儲(chǔ)為
但是這樣存儲(chǔ)方式,換算成十進(jìn)制的值,卻不會(huì)是2.2的,應(yīng)為十進(jìn)制在轉(zhuǎn)換為二進(jìn)制的時(shí)候可能會(huì)不準(zhǔn)確,如2.2,而double類型的數(shù)據(jù)也存在同樣的問(wèn)題,所以在浮點(diǎn)數(shù)表示中會(huì)產(chǎn)生些許的誤差,在單精度轉(zhuǎn)換為雙精度的時(shí)候,也會(huì)存在誤差的問(wèn)題,對(duì)于能夠用二進(jìn)制表示的十進(jìn)制數(shù)據(jù),如2.25,這個(gè)誤差就會(huì)不存在,所以會(huì)出現(xiàn)上面比較奇怪的輸出結(jié)果。
類似地,
doubled=0.6;
floatf=(float)d;
doubled2=f;
boolb=d==d2;
這里的b,也是false。由于float保存23位,而double保存52位,就造成了double轉(zhuǎn)化成float的時(shí)候,丟失掉了一定的數(shù)據(jù),非再轉(zhuǎn)換回去的時(shí)候,那些丟掉的值被補(bǔ)成了0,因此這個(gè)后來(lái)的double和從前的double值已經(jīng)不再一樣了。
那么什么樣的十進(jìn)制小數(shù),表示成二進(jìn)制不是無(wú)限小數(shù)呢?對(duì)于所有的最后一位以5結(jié)尾的十進(jìn)制有限小數(shù),都可以化成二進(jìn)制的有限小數(shù)。
浮點(diǎn)數(shù)與十進(jìn)制之間的互相轉(zhuǎn)換體現(xiàn)在存在內(nèi)存里的數(shù)明顯實(shí)際與0.6不等,但是無(wú)論哪種語(yǔ)言,都能夠在Debug以及輸入的時(shí)候,將它正確的顯示成0.6提供給用戶(程序員),最好的例子就是double和ToString方法,如果我寫doubled=0.59999999999999999999999999999,d.ToString()給我的是0.6。
最后,一個(gè)有意思有問(wèn)題,剛才說(shuō)過(guò)0.6表示成為二進(jìn)制小數(shù)之后,是0.1001并且以1001為循環(huán)節(jié)的無(wú)限循環(huán)小數(shù),那么在我們將它存成浮點(diǎn)數(shù)的時(shí)候,一定會(huì)在某個(gè)位置將它截?cái)?比如float的23位和double的52位),那么真正存在內(nèi)存里的這個(gè)二進(jìn)制數(shù),轉(zhuǎn)化回十進(jìn)制,到底是比原先的十進(jìn)制數(shù)大呢,還是小呢?答案是Itdepends。人計(jì)算十進(jìn)制的時(shí)候,是四舍五入,計(jì)算機(jī)再計(jì)算二進(jìn)制小數(shù)也挺簡(jiǎn)單,就是0舍1入。對(duì)于float,要截?cái)喑蔀?3位,假如卡在24位上的是1,那么就會(huì)造成進(jìn)位,這樣的話,存起來(lái)的值就比真正的十進(jìn)制值大了,如果是0,就舍去,那么存起來(lái)的值就比真正的十進(jìn)制值小了。因此,這可以合理的解釋一個(gè)問(wèn)題,就是0.6d轉(zhuǎn)換成float再轉(zhuǎn)換回double,它的值是0.60000002384185791,這個(gè)值是比0.6大的,原因就是0.6的二進(jìn)制科學(xué)計(jì)數(shù)法表示,第24位是1,造成了進(jìn)位。
愛華網(wǎng)



