1. 程式人生 > >float 和int轉換是怎麼做到的

float 和int轉換是怎麼做到的

轉載自:http://blog.sina.com.cn/s/blog_5c6f79380101bbrd.html

那麼真正存在記憶體裡的這個二進位制數,轉化回十進位制,到底是比原先的十進位制數大呢,還是小呢?答案是It depends。人計算十進位制的時候,是四捨五入,計算機再計算二進位制小數也挺簡單,就是0舍1入。對於float,要截斷成為23位,假如卡在24位上的是1,那麼就會造成進位,這樣的話,存起來的值就比真正的十進位制值大了,如果是0,就捨去,那麼存起來的值就比真正的十進位制值小了。因此,這可以合理的解釋一個問題,就是0.6d轉換成float再轉換回double,它的值是0.60000002384185791,這個值是比0.6大的,原因就是 0.6的二進位制科學計數法表示,第24位是1,造成了進位。

種類——-符號位————-指數位—————-尾數位—-
float—–第31位(佔1bit)—第30-23位(佔8bit)—-第22-0位(佔23bit)
double–第63位(佔1bit)—第62-52位(佔11bit)—第51-0位(佔52bit)

取值範圍主要看指數部分:
float的指數部分有8bit(2^8),由於是有符號型,所以得到對應的指數範圍-128~128。
double的指數部分有11bit(2^11),由於是有符號型,所以得到對應的指數範圍-1024~1024。

由於float的指數部分對應的指數範圍為-128~128,所以取值範圍為:
-2128到2128,約等於-3.4E38 — +3.4E38

精度(有效數字)主要看尾數位:
float的尾數位是23bit,對應7~8位十進位制數,所以有效數字有的編譯器是7位,也有的是8位也即一個整數轉換為float的話,會表示成科學計數法,由小數(精度)和指數構成,對0,1四捨五入。

int,又稱作整型,在.net中特指的是Int32,為32位長度的有符號整型變數。 float,單精度浮點數,32位長度,1位符號位,8位指數位與23位資料位,在.net中又稱為Single。double,64位長度的雙精度浮點數,1位符號位,11位指數位,52位資料位。它們互相的關係就是:int可以穩式轉換成float和double,float只能強制轉換成int,但是可以隱式轉換成double,double只能強制轉換成float和int。

在說明問題之前,還很有必要溫習一下計算機組成原理時學習到的一些知識,就是二進位制補碼錶示以及浮點數表示。我想把一個十進位制轉化為二進位制的方法已經不用多費脣舌,只不過為了計算方便以及消除正零與負零的問題,現代計算機技術,記憶體裡存的都是二進位制的補碼形式,當然這個也沒什麼特別的,只不過有某些離散和點,需要特殊定義而已,比如-(2^31),這個數在int的補碼裡表示成1000…(31個零),這個生套補碼計算公式並不能得到結果(其實不考慮進位的話還真是這個結果,但是總讓人感覺很怪)。再者,浮點數,其實就是把任何二進位制數化成以0.1….開頭的科學計數法表示而已。

廢話說完,這就出現了幾個問題,而且是比較有意思的問題。

1 int i = Int32.MaxValue;

2 float f = i;

3 int j = (int)f;

4 bool b = i == j;

這裡的b,是false。剛才這個操作,如果我們把float換成long,第一次進行隱式轉換,第二次進行強制轉換,結果將會是true。乍一看,float.MaxValue是比int.MaxValue大了不知道多少倍的,然而這個隱式轉換中,卻造成了資料丟失。int.MaxValue,這個值等於231-1,寫成二進位制補碼形式就是01111…(31個1),這個數,在表示成float計數的科學計數法的時候,將會寫成+0.1111…(23個1)*231,對於那31個1,裡面的最後8個,被float無情的拋棄了,因此,再將這個float強制轉換回 int的時候,對應的int的二進位制補碼錶示已經變成了0111…(23個1)00000000,這個數與最初的那個int相差了255,所以造成了不相等。

那麼提出另一個問題,什麼樣的int變成float再變回來,和從前的值相等呢?這個問題其實完全出在那23位float的資料位上了。對於一個int,把它寫成二進位制形式之後,成為了個一32個長度的0、1的排列,對於這個排列,只要第一個1與最後一個1之前的間距,不超過23,那麼它轉換成 float再轉換回來,兩個值就會相等。這個問題是與大小無關的,而且這個集合在int這個全集下並不連續。

1 double d = 0.6;

2 float f = (float)d;

3 double d2 = f;

4 bool b = d == d2;

這裡的b,也是false。剛才這個操作,如果開始另d等於0.5,結果就將會是true。乍一看,0.6這個數這麼短,double和float都肯定能夠表示,那麼轉換過去再轉換回來,結果理應相等。其實這是因為我們用十進位制思考問題太久了,如果我們0.6化成二進位制小數,可以發現得到的結果是0.10011001……(1001迴圈)。這是一個無限迴圈小數。因此,不管float還是double,它在儲存0.6 的時候,都無法完全儲存它精確的值(計算機不懂分數,呵呵),這樣的話由於float儲存23位,而double儲存52位,就造成了double轉化成 float的時候,丟失掉了一定的資料,非再轉換回去的時候,那些丟掉的值被補成了0,因此這個後來的double和從前的double值已經不再一樣了。

這樣就又產生了一個問題,什麼樣的double轉換成float再轉換回來,兩個的值相等呢?其實這個問題與剛才int的那個問題驚人的相似(廢話,都和float打交道,能不相似麼),只不過我們還需要考慮double比float多了3位的指數位,太大的數double能表示但float 不行。

還有一個算是數學上的問題,什麼樣的十進位制小數,表示成二進位制不是無限小數呢?這個問題可以說完全成為數學範疇內的問題了,但是比較簡單,答案也很明顯,對於所有的最後一位以5結尾的十進位制有限小數,都可以化成二進位制的有限小數(雖然這個小數可能長到沒譜)。

最後,一個有意思有問題,剛才說過0.6表示成為二進位制小數之後,是0.1001並且以1001為迴圈節的無限迴圈小數,那麼在我們將它存成浮點數的時候,一定會在某個位置將它截斷(比如float的23位和double的52位),那麼真正存在記憶體裡的這個二進位制數,轉化回十進位制,到底是比原先的十進位制數大呢,還是小呢?答案是It depends。人計算十進位制的時候,是四捨五入,計算機再計算二進位制小數也挺簡單,就是0舍1入。對於float,要截斷成為23位,假如卡在24位上的是1,那麼就會造成進位,這樣的話,存起來的值就比真正的十進位制值大了,如果是0,就捨去,那麼存起來的值就比真正的十進位制值小了。因此,這可以合理的解釋一個問題,就是0.6d轉換成float再轉換回double,它的值是0.60000002384185791,這個值是比0.6大的,原因就是 0.6的二進位制科學計數法表示,第24位是1,造成了進位。
  
  到了這裡,仍然有一事不解,就是對於浮點數,硬體雖然給予了計算上的支援,但是它與十進位制之間的互相轉換,到底是如何做到的呢,又是誰做的呢(彙編器還是編譯器)。這個東西突出體現在存在記憶體裡的數明顯實際與0.6不等,但是無論哪種語言,都能夠在Debug以及輸入的時候,將它正確的顯示成 0.6提供給使用者(程式設計師),最好的例子就是double和ToString方法,如果我寫double d=0.59999999999999999999999999999,d.ToString()給我的是0.6。誠然,對於double來說,我寫的那個N長的數與0.6在記憶體裡存的東西是一樣的,但是計算機,又如果實現了將一個實際與0.6不相等的數變回0.6並顯示給我的呢?


本文來自 tingzhushaohua 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/tingzhushaohua/article/details/76691395?utm_source=copy