淺談浮點數(一)


小數與浮點數

很多人都會認為,小數就是浮點數。但其實非也。
小數只是一種實數的一種特殊表現形式,所有分數都可以用小數來表示。
而浮點數,是計算機領域的一個術語,浮點數代表着目前計算機表示小數的一方式。

浮點數的由來

我們都知道計算機表示特定的數據類型長度是固定的。
比如在java語言里,小數的表示,float是4字節,double是8字節。
那么這些固定長度的二進制位是如何表示小數的呢?
最直觀的表示辦法就是:固定的整數部分位數和固定的小數部分位數。比如以float為例,我們假設取前8位表示整數部分,后24位表示小數部分。則1.2用該方法表示如下:
	00000001 00000000 00000000 00000000 00000010
    以上這種表示小數的方法我們稱之為:定點表示法,即小數點的位置是固定的(這里固定在第24位之前)。
但是這種定點表示法有一個很大的問題,就是表示數的范圍很有限。假設我現在要表示:256.1
那么因為整數部分固定只有8位,將無法表示256,會出現溢出。

   於是乎聰明的計算機科學家想到了另一種辦法:科學計數法。我們知道10進制下的科學計數法可以將一個數表示成: 1.xxx * 10^n 。
依葫蘆畫瓢,那么2進制的科學計數法應該長這樣:1.xxx * 2^n 
那么我們在存儲小數的時候,可以用一部分存儲指數:n,一部分存儲小數:xxx 即可。
而這種表示的方式下,其實小數點沒有固定的位置,既小數點是浮動的。所以我們也就稱這種存儲方式下的數字為浮點數。

浮點數的存儲規范:IEEE 754

  IEEE二進制浮點數算術標准(IEEE 754)是20世紀80年代以來最廣泛使用的浮點數運算標准,為許多CPU與浮點運算器所采用。
這個標准定義了表示浮點數的格式(包括負零-0)與反常值(denormal number)),一些特殊數值(無窮(Inf)與非數值(NaN)),以及這些數值的“浮點數運算符”;
它也指明了四種數值舍入規則和五種例外狀況(包括例外發生的時機與處理方式)。
說人話就是:一個浮點數可以表示如下:
	value = sign x exponent x fraction 
	其中value表示浮點數的實際值
	sign(bit)表示符號位: 0表示整數 1表示負數
	exponent表示的是轉換成科學計數法后的指數偏移值
	fraction表示小數部分
   知道浮點數的具體表示方式之后,接下來就是要確定每一部分所占的長度。
在IEEE 754標准中,對於32位浮點數的各部分長度約定如下:
    ·1bit的sign + 8bit的exponent + 23bit的fraction·
而對於64位的浮點數的各部分長度約定如下:
    ·1bit的sign + 11bit的exponent + 52bit的fraction·

我們前面說過exponent並不是科學計數法之后的實際指數,而是代表科學計數法后的指數偏移量。那么怎么個偏移法呢?
其實在IEEE 754中也對這個做了規定。我們假設k表示exponent所占的總位數,n表示轉換成科學計數法之后的實際指數值,那么最終exponent = 2^(k-1) + n 
    
為什么要這么設計呢?我們知道小數可能是不帶整數的,這時候如果轉換成科學計數法之后實際指數值就應該是負數。
對於指數為負數的情況,我們很自然地會想到用exponent部分的第一位表示正負,然后對於負數值采用補碼的方式來表示(取反加一)。
而原來整個value值也有一個sign位表示正負,剩余位在小數為負數的時候也需要使用補碼方式來表示。
我們假設這樣一種情況:指數為負數且小數為負數,那么對exponent部分的兩次取反加1會導致最終結果不可預知。

	因此,最后IEEE 754采用了:exponent = 2^(k-1) + n 這種方式來存儲指數的偏移值。

java中如何查看浮點數的二進制表示

我們可以使用如下兩行代碼來查看0.1分別在32位和64位下的二級制形式:
     System.out.println(Integer.toString(Float.floatToIntBits(0.1f), 2)); // 111101110011001100110011001101
     System.out.println(Long.toString(Double.doubleToLongBits(0.1), 2)); // 11111110111001100110011001100110011001100110011001100110011010
我們將高位補0,並且按照前面所講的sign + exponent + fraction的形式將兩者拆解如下:
0 01111011    10011001100110011001101
0 01111111011 1001100110011001100110011001100110011001100110011010
要將一個小數轉換成浮點數的形式,首先要求得小數的二進制表示法。0.1的整數部分為0,整數部分的如果用8位表示則為:00000000。
小數部分的0.1如何轉換成2進制呢?這里我們仍然要從10進制小數來進行推導。

我們假設計算機是以10進制的形式來存儲數據的。那么對於0.631,小數部分第1位存儲的應該直接就是6,也就是0.631 * 10 的整數部分。
第2位存儲的應該就是3,也就是 0.31 * 10 (在第一步去掉整數部分之后再乘以10的整數部分)。同理第3位存儲的就是1,0.1 * 10。

於是乎我們可以得到0.1作為二進制在計算機中的存儲:
	第一位: 0.1 * 2 = 0.2 的整數部分  0
	
	第二位: 0.2 * 2 = 0.4 的整數部分  0
	第三位: 0.4 * 2 = 0.8 的整數部分  0
	第四位: 0.8 * 2 = 1.6 的整數部分  1  ---》再去掉整數部分后為0.6
	第五位: 0.6 * 2 = 1.2 的整數部分  1  ---》再去掉整數部分后為0.2
	
	第六為: 0.2 * 2 = 0.4 的整數部分  0
	第七位: 0.4 * 2 = 0.8 的整數部分  0
	第八位: 0.8 * 2 = 1.6 的整數部分  1  ---》再去掉整數部分后為0.6
	第九位: 0.6 * 2 = 1.2 的整數部分  1  ---》再去掉整數部分后為0.2
	
	.....
	
綜上,我們得到0.1的二進制存儲應該為:0001100110011...(0011循環)。
於是,0.1的整個二進制表示為: 00000000.0001100110011...(0011循環)
轉換成科學計數法為:1.100110011...(0011循環) * 2^(-4)。
按照IEEE 754標准,如果是32位的表示法,那么exponent = 2 ^ 7 + (-4) = 01111011
如果是64位表示法,則exponent = 2 ^ 10 + (-4) = 01111111011
再按照 sign + exponent + fraction的表示方法拼接起來即得到32位和64位的表示分別如下:
	0 01111011    100110011001100110011...(0011循環)
	0 01111111011 100110011001100110011001100110011001100110011...(0011循環)

最后剩下的問題就是:小數的存儲位數是固定的,那么如果將循環的部分截斷呢?這就涉及到舍入規則。
舍入的規則如下:即如果左規或右規時丟棄的是0,則舍去不計,反之要將尾數的末尾加1。
我們同樣以0.1為例,32位情況下,小數部分的最終表示如下:10011001100110011001101
我們知道小數部分最后是0011循環,所以最后一位數字本來應該是0,但是因為緊接着的是1,所以最終截取之后還需要進行加1操作,於是就得到1。
64位的表示法同樣也可以根據這個規則得到。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM