我們知道實數是無窮無盡的,然而我們的計算機所能表示的數是有限的,比如32位計算機能表示2的32次方個數,雖然他已經非常多了,但是對於無限的實數來說只是滄海一粟,對於有限位數我們要想表示實數有2種方式。
乙個是定點數,如果我們用4位來表示0~9,即0000->0、0001->1、0010->2…1001->9,那麼32位計算機可以表示乙個8長度的數字,最大值為99999999,如果還得需要表示小數那能表示的數就更小了,由此可見,定點數能表示的數字範圍很小。有沒有其他的辦法來解決這個窘境呢?浮點數就來了。
我們在表達十進位制的時候,我們知道一種表示方法叫科學計數表示法,
10000 = 1.0 * 10^4 = 10.0 * 10^3;
小數點是可以浮動的,所以我們叫他浮點數,其中上邊的公式1.0、10.0我們稱為有效數,4、3稱為指數,計算機是如何儲存浮點數的呢?
先來看乙個簡單的例子
public static void main(string args)
簡簡單單的0.3+0.6,為啥結果是0.8999999999999999而不是0.9呢。要搞清楚這個結果的由來,要先知道浮點數的儲存方式,有乙個ieee 754標準規定了2種浮點數的2種儲存格式,乙個是32位浮點數也就是單精度浮點數,第二個是64位浮點數也就是雙精度浮點數。
按科學計數法二進位制的表示式如下
(−1)?×1.?×2?
其中s叫做符號位,e叫做指數字,f叫做有效位。我們拿32位浮點數來舉例,看下如何儲存上邊的的表示式。
f有效位前邊的1(就是小數點前的1)預設的都是1,稱為隱藏位,無需儲存;細心的你可能會發現,如果用上邊的表示式是無法表示0的,的確,要表示0或者其他特殊值需要e中的0跟255,即指數字全是0或者指數字全為1.具體如下
現在,我們拿乙個具體的數來看下浮點數是如何儲存的,例如十進位制的9.1。按照浮點數的儲存方式我們需要計算出s、e、f分別是多少,要計算出這3個值,我們需要先將9.1轉為二進位制。
第一步先將整數字9轉為二進位制為1001.再將小數字轉為二進位制,和整數的二進位制表示採用「除以 2,然後看餘數」的方式相比,小數部分轉換成二進位制是用乙個相似的反方向操作,就是乘以 2,然後看看是否超過 1。如果超過 1,我們就記下 1,並把結果減去 1,進一步迴圈操作。在這裡,我們就會看到,0.1 其實變成了乙個無限迴圈的二進位制小數,0.000110011。這裡的「0011」會無限迴圈下去。
所以9.1的二進位制為1001.000110011… 轉為浮點數為
(−1)0×1.0010001100110011….×23
由上可知,s=0 正數為0,負數為1,f=00100011001100110011001,最後的乙個「0011」迴圈,因位數的限制,最後乙個「1」會被截斷掉;e的值為3,注意,這裡不是直接拿3轉為二進位制,需要再進行一次計算,因為e的位數是8位一共能表示255個數,既需要表示負數,也要表示正數,同時要扣除2個數(即全是0跟全是1,表示的是特殊數值),所以e能表示的最多是253個數,為了均衡表示數的範圍,所以指數字在 127 之前代表負數,之後代表正數,所以e的3對應的值其實是正數字 127正向偏移3得到 130,想具體了解可以搜尋【移碼】查閱相關資料,總結來說就是移碼無符號位比較方便。130對應的二進位制為10000010,所以e=10000010。整個9.1在32位計算機中儲存的形式就是這樣的
如果你把這個二進位制的浮點數轉換為十進位制會發現他不是等於9.1,他的值等於9.09999942779541015625,可以通過這個鏈結來直觀的計算十進位制的值簡單說下這個怎麼用,只要對應的位是1就勾上就行,然後自動計算出結果
由上我們已經知道浮點數十進位制轉為二進位制的過程及精度丟失的原因,所以為啥0.3+0.6≠0.9就解釋的通了,這是因為,浮點數沒有辦法精確表示 0.3、0.6 和 0.9,而只能取近似值。其實不理解浮點數的儲存有時候會遇到很多完全無法理解的場景,比如下邊的浮點數計算。
我們先來看乙個簡單的例子。
public static void main(string args)
從上邊可以看出+1f直接被忽略了,並沒有加成功,這是為啥呢?想要知道原因我們需要知道浮點數的計算過程,簡單總結就是浮點數的加法需要先指數字對齊,然後再計算,跟十進位制的科學計數法類似,我們拿上面的公式來看下具體計算過程。
其次,指數字對齊,我們先計算他們之間的指數字差值,19000000f的指數字10010111(十進位制151) ,1f的指數字為01111111(十進位制127),通過簡單的計算可以知道他們之間指數字相差151-127=24位,然後如何對齊呢 ,指數小的往大的靠,即指數字較小的數,有效位的小數點進行左移(注意左移的時候需要把隱藏位(即小數點前的1)給考慮進去),在小數點左移的過程中,最右側的有效位就被丟棄掉了
上邊**的截圖可以看出1f在進行指數對齊的時候,右側的有效位移動完之後全變成0了,所以19000000f+1f相當於加0,所以結果還是19000000f。
如果你是一位喜歡實操的人,你可能會嘗試另外一段**
public static void main(string args)
你會發現他的結果是19000004f,如果我們用上邊的知識來計算這段**的結果應該是19000002f才對,怎麼就變成19000004f了呢?,這裡邊涉及到乙個知識點grs及浮點數的捨入運算方式,這裡邊的知識點較細節,大概知道就行,簡單來說就是小數點在左移的過程中,右邊已經捨棄的值,不是直接就扔掉不管了,也是會看具體的值來影響最後結果,有點像四捨五入,比如1.823455四捨五入保留2位小數,不是直接拋棄後邊的3455,而是要看小數字第三位的3是否大於5,同理浮點數在指數進行對齊的時候,右邊捨棄的部分我們可以根據捨棄的具體情況組裝成乙個三位數的值,這個值稱做grs。當grs滿足一定規則的時候,最後一位需要進製,這個就是上邊那個**為何結果是19000004f的原因,有需要了解具體的可以網上搜尋 浮點數的儲存以及 浮點數的比較
浮點數的儲存採用的是近似的原理 float儲存格式為 s e m 1位符號位 8位指數 23位尾數 轉成數值即為 v 1 s 1.m 2 e 127 對於16.5轉成二進位制為00010000.1 1.00001 2 4,那麼在記憶體的表示為 符號位 指數4 127 131 尾數 0 1000001...
浮點數 儲存
關鍵字 體系結構 ieee754 浮點數 儲存 main 如果不執行上面的 讓我們來直接判斷,輸出的結果會是什麼?而在你執行程式之後,結果卻很讓人詫異 123.456001。為什麼會是123.456001?有六位小數可以理解,最後那個1是為何?有很多人解釋說最後那個1是亂碼,隨機的。嘿嘿 其實無論你...
浮點數操作
float fx 49.03f int nx fx 100 printf d nx 執行上述 結果 4902。用vc6.0,2005,gcc編譯執行結果都是一樣。為什麼會這樣呢,是因為浮點數運算具有不精確性。其實編譯上面的 編譯器會有警告的。warning c4244 initializing co...