一起來研究一下浮點數在內存中是怎樣表示的吼不吼哇~
浮點數的分類
大多數語言中將浮點數分為兩大類,32bit和64bit,又叫做float單精度浮點數和double雙精度浮點數。相對來說,double類型的數據要比float類型的數據可以表示的精度更高,數據范圍更廣。
IEEE(電氣電子工程師學會,Institute of Electrical and Electronics Engineers)規定:
- 32位的浮點數,最高的1位是符號位,接着的8位是指數E,剩下的23位為有效數字
- 64位的浮點數,最高的1位是符號位,接着的11位是指數E,剩下的52位為有效數字
選擇切合實際的浮點數類型
可以看出,64位浮點數的指數比32位浮點數多出了三位,表現在數值上則整整多出來(2^11 - 2^8) = 1792位,即二進制里的近一千八百個數量級。用來表示有效數字的位數也翻番了,可以參考int 和 long類型,真的是鳥槍換炮。而它們占用的內存僅僅差了兩倍。
在實際應用中,當對數字的要求不是很高或者有大量數據要處理的話,可以偏重使用float,像GUI里面控制像素點的位置,在誤差不累積的情形下,幾千分之一或者幾百分之一甚至幾十分之一的誤差都是不會直接表現在像素點上的,僅僅一兩位小數就是夠用的。比如同樣對於10,000個像素位置,使用float就要比使用double節省40KB的空間。
而當數據具有以下特點時,就應該好好考慮考慮去使用double類型了:數字被進行頻繁更改,比如加減乘除開方等運算,誤差的不斷累加可能會讓一個float類型的變量表現得很奇怪。或者其他一些對精度要求比較高的情況。
在計算機當中,不僅僅是空間和時間,空間和精度也是互相矛盾的。不過在硬件性能逐漸上升的狀態下,以double為主將成為一種趨勢。
浮點數在內存中的表示
對於一個整數來說,我們都知道,這個數的大小是由2的一些非負整數次冪表示出來的,比如125 = 64 + 32 + 16 + 8 + 4 + 1
那么123的表示就是 1111101。而浮點數的表示就是把小數部分表示成2的一些負數次冪的和,比如0.5表示為.1,0.75表示為.11這樣。就這樣把123.5轉化成二進制 1111011.1 。在實際應用中,出於整齊便於處理等情況的考慮,往往表示成
關於IEEE的標准還有一些細節需要注意:
-
符號位:0表示正,1表示負
-
為了同時將正的和負的指數同時表示為無符號的樣子,需要加一個偏移量,float的偏移量是127,double的是1023。
- 有效數字區域是科學計數法系數的小數部分。由於科學計數法的系數屬於 [1.0, 2.0),所以整數部分沒有必要保存。
這樣,就可以把浮點數分開成三部分進行保存了。
- 符號位, 正數,記為0
- 指數位 -6,記為6 + 127,記為 1000 0101
- 有效數字位, 將整數部分去掉后為1111011,補齊23位為 111 1011 0000 0000 0000 0000
於是,125.5在內存中的二進制表示為 0100 0010 1111 1011 0000 0000 0000 0000,看到這個32位二進制串,很容易聯想到這也可以表示一個int值,進制轉換之后得到1123745792。(Windows10 的計算器有程序員模式哦)
接下來,我們就驗證一下。
一些驗證步驟
這里要用到C/C++里面的union,就是union中的變量的值在同一塊內存。比如 0110 0001是一個union {int integer, char character}的值, 那么作為兩種數據類型去使用將分別得到 97 和 ‘a’。
代碼如下:
#include <stdio.h> int main() { union Bin{ int i; float f; }; union Bin unionBin; unionBin.f = 125.5; printf("%d\n", unionBin.i); printf("%f\n", unionBin.f); return 0; }
輸出結果:
1123745792
125.500000
我們的驗證就結束了。
在油管上找到一個很好玩的頻道Computerphile,有很多和計算機有關的知識,偶然間看到了浮點數在內存中的表示。突然想起來曾經對此好奇過,但是之后就不了了之了,決定今天了結它。
個人網站:鍋爐工的鍋爐房
Github主頁:boileryao@github,歡迎star。